diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index f1e385f..0000000 --- a/.editorconfig +++ /dev/null @@ -1,223 +0,0 @@ -# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行 -root = true - -[*] -charset = utf-8 -end_of_line = crlf -insert_final_newline = true - -[*.cs] - -#### Core EditorConfig 选项 #### - -# 缩进和间距 -indent_size = 4 -indent_style = space -tab_width = 4 - -#### .NET 编码约定 #### - -# 组织 Using -dotnet_separate_import_directive_groups = false -dotnet_sort_system_directives_first = true -file_header_template = unset - -# this. 和 Me. 首选项 -dotnet_style_qualification_for_event = false:suggestion -dotnet_style_qualification_for_field = false:suggestion -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_property = false:suggestion - -# 语言关键字与 BCL 类型首选项 -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion -dotnet_style_predefined_type_for_member_access = true:suggestion - -# 括号首选项 -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_operators = never_if_unnecessary -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity - -# 修饰符首选项 -dotnet_style_require_accessibility_modifiers = for_non_interface_members - -# 表达式级首选项 -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_prefer_inferred_tuple_names = true:suggestion -dotnet_style_operator_placement_when_wrapping = beginning_of_line -dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_prefer_compound_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion -dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion -dotnet_style_prefer_simplified_boolean_expressions = true:suggestion -dotnet_style_prefer_simplified_interpolation = true:suggestion - -# 字段首选项 -dotnet_style_readonly_field = true - -# 参数首选项 -dotnet_code_quality_unused_parameters = all - -#### C# 编码约定 #### - -# var 首选项 -csharp_style_var_elsewhere = true:suggestion -csharp_style_var_for_built_in_types = true:suggestion -csharp_style_var_when_type_is_apparent = true:suggestion - -# Expression-bodied 成员 -csharp_style_expression_bodied_accessors = true:suggestion -# csharp_style_expression_bodied_constructors = true:suggestion -csharp_style_expression_bodied_indexers = true:suggestion -csharp_style_expression_bodied_lambdas = true:suggestion -csharp_style_expression_bodied_local_functions = true:suggestion -# csharp_style_expression_bodied_methods = true:suggestion -csharp_style_expression_bodied_operators = true:suggestion -csharp_style_expression_bodied_properties = true:suggestion - -# 模式匹配首选项 -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_prefer_not_pattern = true:suggestion -csharp_style_prefer_pattern_matching = true:suggestion -csharp_style_prefer_switch_expression = true:suggestion - -# null 检查首选项 -csharp_style_conditional_delegate_call = true:suggestion - -# 修饰符首选项 -csharp_prefer_static_local_function = true:suggestion -csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async - -# 代码块首选项 -csharp_prefer_braces = false:silent -csharp_prefer_simple_using_statement = true:suggestion - -# 表达式级首选项 -csharp_prefer_simple_default_expression = true:suggestion -csharp_style_deconstructed_variable_declaration = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion -csharp_style_prefer_index_operator = true:suggestion -csharp_style_prefer_range_operator = true:suggestion -csharp_style_throw_expression = true:suggestion -csharp_style_unused_value_assignment_preference = discard_variable:suggestion -csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion -csharp_style_pattern_local_over_anonymous_function = true:suggestion - -# using 指令首选项 -csharp_using_directive_placement = outside_namespace:suggestion - -#### C# 格式规则 #### - -# 新行首选项 -csharp_new_line_before_catch = true -csharp_new_line_before_else = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_open_brace = all -csharp_new_line_between_query_expression_clauses = true - -# 缩进首选项 -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = false -csharp_indent_labels = one_less_than_current -csharp_indent_switch_labels = true - -# 空格键首选项 -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - -# 包装首选项 -csharp_preserve_single_line_blocks = true -csharp_preserve_single_line_statements = true - -#### 命名样式 #### - -# 命名规则 - -dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion -dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface -dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i - -dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.types_should_be_pascal_case.symbols = types -dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case - -dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members -dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case - -dotnet_naming_rule.visible_field_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.visible_field_should_be_pascal_case.symbols = visible_field -dotnet_naming_rule.visible_field_should_be_pascal_case.style = pascal_case - -dotnet_naming_rule.const_field_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.const_field_should_be_pascal_case.symbols = const_field -dotnet_naming_rule.const_field_should_be_pascal_case.style = pascal_case - -dotnet_naming_rule.private_field_should_be_camel_begin_with__.severity = suggestion -dotnet_naming_rule.private_field_should_be_camel_begin_with__.symbols = private_field -dotnet_naming_rule.private_field_should_be_camel_begin_with__.style = camel_begin_with__ - -# 符号规范 - -dotnet_naming_symbols.interface.applicable_kinds = interface -dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected - -dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum -dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected - -dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method -dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected - -dotnet_naming_symbols.const_field.applicable_kinds = field -dotnet_naming_symbols.const_field.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.const_field.required_modifiers = const - -dotnet_naming_symbols.visible_field.applicable_kinds = field -dotnet_naming_symbols.visible_field.applicable_accessibilities = public, internal, protected_internal - -dotnet_naming_symbols.private_field.applicable_kinds = field -dotnet_naming_symbols.private_field.applicable_accessibilities = private - -# 命名样式 - -dotnet_naming_style.pascal_case.capitalization = pascal_case - -dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.capitalization = pascal_case - -dotnet_naming_style.camel_begin_with__.required_prefix = _ -dotnet_naming_style.camel_begin_with__.capitalization = camel_case - -# ReSharper properties -resharper_max_initializer_elements_on_line = 1 diff --git a/Flandre.sln b/Flandre.sln index 3f5bf95..58ea566 100644 --- a/Flandre.sln +++ b/Flandre.sln @@ -1,106 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.4.33213.308 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Core", "src\Flandre.Core\Flandre.Core.csproj", "{347EE5CE-CAB6-4FFA-AAF1-D7AA7BC6AFF7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Adapters.Konata", "src\Flandre.Adapters.Konata\Flandre.Adapters.Konata.csproj", "{723E8DD2-55F1-4095-93D7-E6AD600A63EE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Adapters.Mock", "src\Flandre.Adapters.Mock\Flandre.Adapters.Mock.csproj", "{18C926FE-1F45-421C-A19B-BB914DBD7437}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Adapters.OneBot", "src\Flandre.Adapters.OneBot\Flandre.Adapters.OneBot.csproj", "{49047181-70A7-44F9-BC70-992BD0289C97}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Framework", "src\Flandre.Framework\Flandre.Framework.csproj", "{76195FF7-5683-451D-9D22-955CE0CA20EF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Framework.Tests", "tests\Flandre.Framework.Tests\Flandre.Framework.Tests.csproj", "{23B5856D-C230-4EC6-A3C0-A31E56AABFC9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Core.Tests", "tests\Flandre.Core.Tests\Flandre.Core.Tests.csproj", "{99CA905D-7B6C-4615-BF50-F06CAB14941D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Core.Reactive", "src\Flandre.Core.Reactive\Flandre.Core.Reactive.csproj", "{B8597CBB-A7A0-4F0F-BA49-3B2D34557327}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Framework.Reactive", "src\Flandre.Framework.Reactive\Flandre.Framework.Reactive.csproj", "{85F42336-C536-45F8-BFFD-A75E1E56B4AC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flandre.Core.Reactive.Tests", "tests\Flandre.Core.Reactive.Tests\Flandre.Core.Reactive.Tests.csproj", "{43701E39-49AD-4342-B66F-773D76B35438}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFolder", "SolutionFolder", "{D59B439D-ECB9-460B-9F28-FE913CCFA6E5}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitignore = .gitignore - LICENSE = LICENSE - README.md = README.md - README.NuGet.md = README.NuGet.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Adapters.Discord", "src\Flandre.Adapters.Discord\Flandre.Adapters.Discord.csproj", "{CF294883-2C88-4AF9-BED6-8F766C664982}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Adapters.Konata.Extensions", "src\Flandre.Adapters.Konata.Extensions\Flandre.Adapters.Konata.Extensions.csproj", "{3BEF4654-51CB-47FA-9197-698F3B8B23BA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Adapters.OneBot.Extensions", "src\Flandre.Adapters.OneBot.Extensions\Flandre.Adapters.OneBot.Extensions.csproj", "{3F5443F7-2B58-42E7-A5D6-8E88594724E4}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {347EE5CE-CAB6-4FFA-AAF1-D7AA7BC6AFF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {347EE5CE-CAB6-4FFA-AAF1-D7AA7BC6AFF7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {347EE5CE-CAB6-4FFA-AAF1-D7AA7BC6AFF7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {347EE5CE-CAB6-4FFA-AAF1-D7AA7BC6AFF7}.Release|Any CPU.Build.0 = Release|Any CPU - {723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Release|Any CPU.Build.0 = Release|Any CPU - {18C926FE-1F45-421C-A19B-BB914DBD7437}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {18C926FE-1F45-421C-A19B-BB914DBD7437}.Debug|Any CPU.Build.0 = Debug|Any CPU - {18C926FE-1F45-421C-A19B-BB914DBD7437}.Release|Any CPU.ActiveCfg = Release|Any CPU - {18C926FE-1F45-421C-A19B-BB914DBD7437}.Release|Any CPU.Build.0 = Release|Any CPU - {49047181-70A7-44F9-BC70-992BD0289C97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {49047181-70A7-44F9-BC70-992BD0289C97}.Debug|Any CPU.Build.0 = Debug|Any CPU - {49047181-70A7-44F9-BC70-992BD0289C97}.Release|Any CPU.ActiveCfg = Release|Any CPU - {49047181-70A7-44F9-BC70-992BD0289C97}.Release|Any CPU.Build.0 = Release|Any CPU - {76195FF7-5683-451D-9D22-955CE0CA20EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76195FF7-5683-451D-9D22-955CE0CA20EF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76195FF7-5683-451D-9D22-955CE0CA20EF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76195FF7-5683-451D-9D22-955CE0CA20EF}.Release|Any CPU.Build.0 = Release|Any CPU - {23B5856D-C230-4EC6-A3C0-A31E56AABFC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23B5856D-C230-4EC6-A3C0-A31E56AABFC9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23B5856D-C230-4EC6-A3C0-A31E56AABFC9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23B5856D-C230-4EC6-A3C0-A31E56AABFC9}.Release|Any CPU.Build.0 = Release|Any CPU - {99CA905D-7B6C-4615-BF50-F06CAB14941D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99CA905D-7B6C-4615-BF50-F06CAB14941D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99CA905D-7B6C-4615-BF50-F06CAB14941D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99CA905D-7B6C-4615-BF50-F06CAB14941D}.Release|Any CPU.Build.0 = Release|Any CPU - {B8597CBB-A7A0-4F0F-BA49-3B2D34557327}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B8597CBB-A7A0-4F0F-BA49-3B2D34557327}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B8597CBB-A7A0-4F0F-BA49-3B2D34557327}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B8597CBB-A7A0-4F0F-BA49-3B2D34557327}.Release|Any CPU.Build.0 = Release|Any CPU - {85F42336-C536-45F8-BFFD-A75E1E56B4AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85F42336-C536-45F8-BFFD-A75E1E56B4AC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85F42336-C536-45F8-BFFD-A75E1E56B4AC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85F42336-C536-45F8-BFFD-A75E1E56B4AC}.Release|Any CPU.Build.0 = Release|Any CPU - {43701E39-49AD-4342-B66F-773D76B35438}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43701E39-49AD-4342-B66F-773D76B35438}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43701E39-49AD-4342-B66F-773D76B35438}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43701E39-49AD-4342-B66F-773D76B35438}.Release|Any CPU.Build.0 = Release|Any CPU - {CF294883-2C88-4AF9-BED6-8F766C664982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF294883-2C88-4AF9-BED6-8F766C664982}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF294883-2C88-4AF9-BED6-8F766C664982}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF294883-2C88-4AF9-BED6-8F766C664982}.Release|Any CPU.Build.0 = Release|Any CPU - {3BEF4654-51CB-47FA-9197-698F3B8B23BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3BEF4654-51CB-47FA-9197-698F3B8B23BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3BEF4654-51CB-47FA-9197-698F3B8B23BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3BEF4654-51CB-47FA-9197-698F3B8B23BA}.Release|Any CPU.Build.0 = Release|Any CPU - {3F5443F7-2B58-42E7-A5D6-8E88594724E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F5443F7-2B58-42E7-A5D6-8E88594724E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F5443F7-2B58-42E7-A5D6-8E88594724E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F5443F7-2B58-42E7-A5D6-8E88594724E4}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F1C8B6A9-12B8-430E-BEA3-62D7C5161683} - EndGlobalSection EndGlobal diff --git a/Flandre.sln.DotSettings b/Flandre.sln.DotSettings deleted file mode 100644 index 2911a76..0000000 --- a/Flandre.sln.DotSettings +++ /dev/null @@ -1,3 +0,0 @@ - - True - True \ No newline at end of file diff --git a/README.NuGet.md b/README.NuGet.md deleted file mode 100644 index 966d293..0000000 --- a/README.NuGet.md +++ /dev/null @@ -1,23 +0,0 @@ -# Flandre - -.NET 6 实现的跨平台,现代化聊天机器人框架 -一套代码,多平台服务 - -[![License](https://img.shields.io/github/license/FlandreDevs/Flandre?label=License&style=flat&color=42a5f5)](https://github.com/FlandreDevs/Flandre/blob/main/LICENSE) -[![Stars](https://img.shields.io/github/stars/FlandreDevs/Flandre?label=Stars&style=flat&color=1976d2)](https://github.com/FlandreDevs/Flandre/stargazers) -[![Contributors](https://img.shields.io/github/contributors/FlandreDevs/Flandre?label=Contributors&style=flat&color=9866ca)](https://github.com/FlandreDevs/Flandre/graphs/contributors) -[![.NET Version](https://img.shields.io/badge/.NET-6-ffe57f?style=flat)](https://www.nuget.org/packages/Flandre.Core/) -[![Codecov](https://img.shields.io/codecov/c/gh/FlandreDevs/Flandre/dev?style=flat&color=a5d6a7&label=Coverage)](https://app.codecov.io/gh/FlandreDevs/Flandre) - -\- **[使用文档](https://flandredevs.github.io/)** - - -本项目的名称来源于东方 Project 中的角色芙兰朵露 · 斯卡雷特 (Flandre Scarlet) ~~(番茄炒蛋)~~ - ---- - -**项目的完整 README 可[在 GitHub 上查看](https://github.com/FlandreDevs/Flandre/)。** - ---- - -**项目仍在早期开发阶段,功能尚未完善,且处于快速迭代过程中。** -**如果您对项目的开发感兴趣,诚挚欢迎您的改进建议或 PR 贡献。** diff --git a/README.md b/README.md deleted file mode 100644 index 4ab80ac..0000000 --- a/README.md +++ /dev/null @@ -1,154 +0,0 @@ -
- - - -# Flandre - -.NET 6 实现的跨平台,现代化聊天机器人框架 -一套代码,多平台服务 - -[![License](https://img.shields.io/github/license/FlandreDevs/Flandre?label=License&style=flat&color=42a5f5)](https://github.com/FlandreDevs/Flandre/blob/main/LICENSE) -[![Stars](https://img.shields.io/github/stars/FlandreDevs/Flandre?label=Stars&style=flat&color=1976d2)](https://github.com/FlandreDevs/Flandre/stargazers) -[![Contributors](https://img.shields.io/github/contributors/FlandreDevs/Flandre?label=Contributors&style=flat&color=9866ca)](https://github.com/FlandreDevs/Flandre/graphs/contributors) -[![Flandre.Framework Version](https://img.shields.io/nuget/vpre/Flandre.Framework?style=flat&label=Framework&color=f06292)](https://www.nuget.org/packages/Flandre.Framework/) -[![Flandre.Core Version](https://img.shields.io/nuget/vpre/Flandre.Core?style=flat&label=Core&color=e65943)](https://www.nuget.org/packages/Flandre.Core/) -[![.NET Version](https://img.shields.io/badge/.NET-6-ffe57f?style=flat)](https://www.nuget.org/packages/Flandre.Core/) -[![Codecov](https://img.shields.io/codecov/c/gh/FlandreDevs/Flandre/dev?style=flat&color=a5d6a7&label=Coverage)](https://app.codecov.io/gh/FlandreDevs/Flandre) - -\- **[使用文档](https://flandre.sorabs.cc/)** - - -本项目的名称来源于东方 Project 中的角色芙兰朵露 · 斯卡雷特 (Flandre Scarlet) ~~(番茄炒蛋)~~ - -
- ---- - -## ⭐ 特性 - -### 🌐 原生跨平台 - -Flandre -为跨平台而生,对聊天平台的结构进行抽象化,采用适配器模式进行兼容,使得开发者可以通过一套统一接口控制不同平台的机器人,同时提供了良好的开发体验。 -目前已经实现的适配器: - -| 平台 | 介绍 | -|:-----------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------:| -| [OneBot](https://github.com/FlandreDevs/Flandre/blob/dev/src/Flandre.Adapters.OneBot/README.md) | [OneBot](https://github.com/botuniverse/onebot) v11 协议封装,主要对 [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 提供支持。支持 QQ 协议,同时基于 go-cqhttp 对 QQ 频道也进行了一定的支持。 | -| [Konata](https://github.com/FlandreDevs/Flandre/blob/dev/src/Flandre.Adapters.Konata/README.md) | QQ 协议适配,基于 [Konata.Core](https://github.com/KonataDev/Konata.Core) | -| Discord | Discord 适配,基于 [Discord.Net](https://github.com/discord-net/Discord.Net) **(Work in Progress)** | -| Telegram | 计划中... | - -### 🧩 灵活的开发方式 - -Flandre 提供两种开发方式,分别是完整的开发框架 `Framework`,以及易于嵌入已有程序的 `Core`。 - -
-详细区别 - -#### Flandre.Framework - -[![NuGet](https://img.shields.io/nuget/vpre/Flandre.Framework?style=flat&label=NuGet&color=9866ca)](https://www.nuget.org/packages/Flandre.Framework/) -[![NuGet Downloads](https://img.shields.io/nuget/dt/Flandre.Framework?style=flat&label=Downloads&color=42a5f5)](https://www.nuget.org/packages/Flandre.Framework/) - -`Flandre.Framework` 是一个使用方便、功能全面的 Bot 开发框架,在核心包 `Core` -的基础上集成了插件、指令、中间件等系统,并提供依赖注入、日志管理等等实用功能。 -Framework 基于 [Microsoft.Extensions.Hosting](https://learn.microsoft.com/zh-cn/dotnet/core/extensions/generic-host) -,这意味着可以复用大量社区已有的开源库。在 API 设计上,Framework 参考了 ASP.NET Core,如果你有相关经验,使用起来会更加得心应手。 - -对于一个全新的 Bot 项目,我们建议直接使用 Framework 开发。 - -#### Flandre.Core - -[![NuGet](https://img.shields.io/nuget/vpre/Flandre.Core?style=flat&label=NuGet&color=9866ca)](https://www.nuget.org/packages/Flandre.Core/) -[![NuGet Downloads](https://img.shields.io/nuget/dt/Flandre.Core?style=flat&label=Downloads&color=42a5f5)](https://www.nuget.org/packages/Flandre.Core/) - -`Flandre.Core` 是整个框架的核心组件,包含了适配器、机器人等抽象层,提供直接操作 Bot -进行平台交互的功能。相比 `Framework`,`Core` 作为一个轻量化的模块,能更容易地嵌入进已有项目中,成为功能的一部分。 - -> 不需要代入 .NET Framework / Core 命名方式的意义。在 Flandre 中,两者只意味着开发方式的不同,且都处于积极维护中。 - -
- -下文将主要介绍 `Flandre.Framework` 的各类特性。如果你需要关于 `Flandre.Core` 的详细说明,请~~参照这里的文档~~。(还没写x) - -### 📦 开箱即用的指令系统 - -Flandre.Framework 实现了一套开箱即用的指令解析系统,而无需开发者自己造轮子。开发者可以方便地定义一条指令: - -```csharp -[Command] -public MessageContent Add(double a, double b) -{ - var result = a + b; - return $"{a} + {b} = {result}"; -} -``` - -- 用户发送 `add 1.14514 1.91981` -- 机器人返回 `1.14514 + 1.91981 = 3.06495` - -## 🚀 起步 - -我们提供了一个模板项目,可以帮助你快速上手。([仓库在这里](https://github.com/FlandreDevs/Templates)) - -首先安装模板包: - -```shell -$ dotnet new install Flandre.Templates - -# 如果曾经安装过,可以使用以下命令更新至最新版本 -$ dotnet new update -``` - -创建一个名为 `MyFirstFlandreApp` 的新项目: - -```shell -$ dotnet new flandre -o MyFirstFlandreApp -``` - -使用你喜欢的 IDE 打开项目,在 `Program.cs` 中添加想要的适配器,开始对 Flandre 的探索。 - -需要一个示例?[Sakuya](https://github.com/FlandreDevs/Sakuya) 是我们提供的一个示例项目,可以前往查看。 - -## 💻 开发 - -你可以查看本仓库的[里程碑](https://github.com/FlandreDevs/Flandre/milestones) -或[项目](https://github.com/FlandreDevs/Flandre/projects)页,获取最新的开发进度。 - -如果你在开发的过程中发现了 Bug,或有建议,欢迎[提交 Issue](https://github.com/FlandreDevs/Flandre/issues/new/choose)。 - -如果你想要贡献代码,欢迎[与我们联系](#%F0%9F%92%AC%20%E4%BA%A4%E6%B5%81) -,并[发起 PR](https://github.com/FlandreDevs/Flandre/compare)。 - -项目的主要分支: - -- `dev` 分支 - 默认开发分支,包含最新更改,但可能不稳定。 -- `feat/*` 分支 - 包含各种特性的单独开发分支,通常在完成后会向 `dev` 分支发起 PR -- `release` 分支 - 包含上一个发布版本的源代码,`dev` 分支会在版本发布时合并过来 - -向仓库贡献代码时,请确保目标是 `dev` 分支。 - -## ❤️ 致谢 - -项目编写过程中参考了许多开源项目,没有它们就没有 Flandre 的诞生: - -- [koishijs/koishi](https://github.com/koishijs/koishi) -- [KonataDev/Konata.Core](https://github.com/KonataDev/Konata.Core) - -(按字母排序) - -另外,感谢 [JetBrains](https://www.jetbrains.com/) 对本项目的支持,以及免费授权的产品开源使用许可! - -JetBrains Logo (Main) logo. - -## 💬 交流 - -GitHub 是我们的主要活动场地。您也可以加入我们的 QQ 群进行项目相关的交流: - -[![QQ](https://img.shields.io/badge/Flandre.Community-164189664-blue?style=flat&logo=tencent-qq&logoColor=white)](https://jq.qq.com/?_wv=1027&k=tTNVlDR6) - -本群只交流程序开发,拒绝任何形式的伸手党或商业行为。 - -## 📄 开源 - -本项目以 [MIT 许可证](https://github.com/FlandreDevs/Flandre/blob/main/LICENSE) 开源 (′▽\`)╭(′▽\`)╯ diff --git a/assets/avatar.jpg b/assets/avatar.jpg deleted file mode 100644 index d3f5689..0000000 Binary files a/assets/avatar.jpg and /dev/null differ diff --git a/scripts/release.ps1 b/scripts/release.ps1 deleted file mode 100644 index 5c07e97..0000000 --- a/scripts/release.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -git switch release -git merge dev -git push origin release -git switch dev diff --git a/src/Flandre.Adapters.Discord/DiscordAdapter.cs b/src/Flandre.Adapters.Discord/DiscordAdapter.cs deleted file mode 100644 index cdcad94..0000000 --- a/src/Flandre.Adapters.Discord/DiscordAdapter.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Flandre.Core.Common; - -namespace Flandre.Adapters.Discord; - -/// -/// Discord 适配器 -/// -public sealed class DiscordAdapter : IAdapter -{ - /// - public IEnumerable Bots => _bots.AsReadOnly(); - - private readonly List _bots = new(); - - /// - /// 构造 Discord 适配器 - /// - /// - public DiscordAdapter(DiscordAdapterConfig config) - { - foreach (var bot in config.Bots) - { - _bots.Add(new DiscordBot(bot, config.Proxy)); - } - } - - /// - public Task StartAsync() - { - return Task.WhenAll(_bots.Select(bot => bot.StartAsync())); - } - - /// - public async Task StopAsync() - { - } -} - -/// -/// Discord 适配器配置 -/// -public class DiscordAdapterConfig -{ - /// - /// bot 配置列表 - /// - public List Bots { get; set; } = new(); - - /// - /// 代理地址,为空则不使用代理 - /// - public string? Proxy { get; set; } = null; -} diff --git a/src/Flandre.Adapters.Discord/DiscordBot.cs b/src/Flandre.Adapters.Discord/DiscordBot.cs deleted file mode 100644 index a07305f..0000000 --- a/src/Flandre.Adapters.Discord/DiscordBot.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Net; -using Discord; -using Discord.Net.Rest; -using Discord.Net.WebSockets; -using Discord.WebSocket; -using Flandre.Core.Common; -using Flandre.Core.Events; -using Flandre.Core.Messaging; - -namespace Flandre.Adapters.Discord; - -/// -/// Discord Bot -/// -public sealed partial class DiscordBot : Bot -{ - /// - public override string Platform => "discord"; - - /// - public override string SelfId { get; } - - /// - /// Discord Client - /// - /// - public DiscordSocketClient Internal { get; } - - private readonly DiscordBotConfig _config; - - /// - public override event BotEventHandler? MessageReceived; - - /// - public override event BotEventHandler? GuildInvited; - - /// - public override event BotEventHandler? GuildJoinRequested; - - /// - public override event BotEventHandler? FriendRequested; - - internal DiscordBot(DiscordBotConfig config, string? proxy) - { - _config = config; - Internal = new DiscordSocketClient(new DiscordSocketConfig - { - GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent, - RestClientProvider = DefaultRestClientProvider.Create(proxy is not null), - WebSocketProvider = DefaultWebSocketProvider.Create(proxy is null ? null : new WebProxy(proxy)) - }); - SelfId = config.SelfId; - - // See InternalEventHandlers.cs - Internal.Log += InternalOnLog; - Internal.MessageReceived += InternalOnMessageReceived; - } - - /// - public override async Task StartAsync() - { - if (_config.Token is null) - throw new DiscordException("Bot token cannot be null."); - - await Internal.LoginAsync(TokenType.Bot, _config.Token); - await Internal.StartAsync(); - } - - /// - public override async Task SendChannelMessageAsync(string channelId, MessageContent content, - string? guildId = null) - { - if (!InternalUtils.CheckIsValidId(channelId, out var parsed, this, "channel")) - return null; - - var channel = await Internal.GetChannelAsync(parsed); - if (channel is not SocketTextChannel textChannel) - return null; - - var msg = await textChannel.SendMessageAsync(content.GetText()); - return msg?.Id.ToString(); - } - - /// - public override async Task SendPrivateMessageAsync(string userId, MessageContent content) - { - if (!InternalUtils.CheckIsValidId(userId, out var parsed, this, "user")) - return null; - - var user = await Internal.GetUserAsync(parsed); - var msg = await user.SendMessageAsync(content.GetText()); - return msg?.Id.ToString(); - } -} diff --git a/src/Flandre.Adapters.Discord/DiscordBotConfig.cs b/src/Flandre.Adapters.Discord/DiscordBotConfig.cs deleted file mode 100644 index 5ac0323..0000000 --- a/src/Flandre.Adapters.Discord/DiscordBotConfig.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Flandre.Core.Common; - -namespace Flandre.Adapters.Discord; - -/// -/// Discord Bot 配置 -/// -public class DiscordBotConfig : BotConfig -{ - /// - /// Bot Token - /// - public string? Token { get; set; } -} diff --git a/src/Flandre.Adapters.Discord/DiscordException.cs b/src/Flandre.Adapters.Discord/DiscordException.cs deleted file mode 100644 index 5fb393e..0000000 --- a/src/Flandre.Adapters.Discord/DiscordException.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Flandre.Adapters.Discord; - -/// -/// Discord 异常 -/// -public sealed class DiscordException : Exception -{ - internal DiscordException(string message) : base(message) { } -} diff --git a/src/Flandre.Adapters.Discord/Flandre.Adapters.Discord.csproj b/src/Flandre.Adapters.Discord/Flandre.Adapters.Discord.csproj deleted file mode 100644 index 52949a6..0000000 --- a/src/Flandre.Adapters.Discord/Flandre.Adapters.Discord.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - Flandre.Adapters.Discord - 0.1.1 - FlandreDevs,bsdayo - Discord adapter for Flandre project, based on Discord.Net. - bot;chatbot;flandre;adapter;discord - MIT - avatar.jpg - - net6.0 - enable - enable - Library - true - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2023 - - - - - - - - - - - - - - - diff --git a/src/Flandre.Adapters.Discord/InternalEventHandlers.cs b/src/Flandre.Adapters.Discord/InternalEventHandlers.cs deleted file mode 100644 index 5e6b0d0..0000000 --- a/src/Flandre.Adapters.Discord/InternalEventHandlers.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Discord; -using Discord.WebSocket; -using Flandre.Core.Common; -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Core.Models; - -namespace Flandre.Adapters.Discord; - -public sealed partial class DiscordBot -{ - private Task InternalOnLog(LogMessage log) - { - var logLevel = log.Severity switch - { - LogSeverity.Critical => BotLogLevel.Critical, - LogSeverity.Error => BotLogLevel.Error, - LogSeverity.Warning => BotLogLevel.Warning, - LogSeverity.Info => BotLogLevel.Information, - LogSeverity.Verbose => BotLogLevel.Trace, - LogSeverity.Debug => BotLogLevel.Debug, - - _ => BotLogLevel.Debug - }; - - Log(logLevel, log.Message); - if (log.Exception is { } ex) - Log(logLevel, ex.ToString()); - - return Task.CompletedTask; - } - - private Task InternalOnMessageReceived(SocketMessage message) - { - MessageReceived?.Invoke(this, new BotMessageReceivedEvent(new Message - { - Time = message.Timestamp.DateTime, - Platform = Platform, - Environment = message.Channel is SocketDMChannel ? MessageEnvironment.Private : MessageEnvironment.Channel, - MessageId = message.Id.ToString(), - GuildId = message.Author is IGuildUser gu ? gu.Guild.Id.ToString() : null, - ChannelId = message.Channel.Id.ToString(), - Sender = new User - { - Name = message.Author.Username, - Nickname = message.Author.Username, - UserId = message.Author.Id.ToString(), - AvatarUrl = message.Author.GetAvatarUrl() - }, - Content = message.Content - })); - - return Task.CompletedTask; - } -} diff --git a/src/Flandre.Adapters.Discord/InternalUtils.cs b/src/Flandre.Adapters.Discord/InternalUtils.cs deleted file mode 100644 index 42931f8..0000000 --- a/src/Flandre.Adapters.Discord/InternalUtils.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Flandre.Core.Common; - -namespace Flandre.Adapters.Discord; - -internal static class InternalUtils -{ - internal static bool CheckIsValidId(string id, out ulong parsed, Bot bot, string idType) - { - var result = ulong.TryParse(id, out parsed); - if (!result) - bot.Log(BotLogLevel.Warning, - $"Invalid {idType} id passed to Discord API. Must be parsable to uint64. Ignoring."); - return result; - } -} diff --git a/src/Flandre.Adapters.Konata.Extensions/AdapterCollectionExtensions.cs b/src/Flandre.Adapters.Konata.Extensions/AdapterCollectionExtensions.cs deleted file mode 100644 index d9628c1..0000000 --- a/src/Flandre.Adapters.Konata.Extensions/AdapterCollectionExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Flandre.Framework; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Flandre.Adapters.Konata.Extensions; - -/// -/// Konata 适配器扩展 -/// -public static class AdapterCollectionExtensions -{ - /// - /// 添加 Konata 适配器,自动从配置根中的 Adapters:Konata 项读取配置。 - /// - public static void AddKonata(this IAdapterCollection adapters) - { - var config = adapters.Services - .BuildServiceProvider() - .GetRequiredService() - .GetSection("Adapters:Konata") - .Get(); - adapters.Add(new KonataAdapter(config ?? new KonataAdapterConfig())); - } - - /// - /// 添加 Konata 适配器。 - /// - public static void AddKonata(this IAdapterCollection adapters, IConfiguration configuration) - { - var config = configuration.Get(); - adapters.Add(new KonataAdapter(config ?? new KonataAdapterConfig())); - } - - /// - /// 添加 Konata 适配器。 - /// - public static void AddKonata(this IAdapterCollection adapters, Action action) - { - var config = new KonataAdapterConfig(); - action(config); - adapters.Add(new KonataAdapter(config)); - } - - /// - /// 添加 Konata 适配器。 - /// - public static void AddKonata(this IAdapterCollection adapters, KonataAdapterConfig config) - { - adapters.Add(new KonataAdapter(config)); - } -} diff --git a/src/Flandre.Adapters.Konata.Extensions/Flandre.Adapters.Konata.Extensions.csproj b/src/Flandre.Adapters.Konata.Extensions/Flandre.Adapters.Konata.Extensions.csproj deleted file mode 100644 index 5371345..0000000 --- a/src/Flandre.Adapters.Konata.Extensions/Flandre.Adapters.Konata.Extensions.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - Flandre.Adapters.Konata.Extensions - 2.0.0-rc.3 - FlandreDevs,bsdayo - Flandre.Framework extensions for Flandre.Adapters.Konata. - bot;chatbot;flandre;adapter;konata;extensions - GPL-3.0-only - avatar.jpg - - net6.0 - enable - enable - Library - true - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2022-2023 - - - - - - - - - - - - diff --git a/src/Flandre.Adapters.Konata/Flandre.Adapters.Konata.csproj b/src/Flandre.Adapters.Konata/Flandre.Adapters.Konata.csproj deleted file mode 100644 index a538d71..0000000 --- a/src/Flandre.Adapters.Konata/Flandre.Adapters.Konata.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - - Flandre.Adapters.Konata - 2.0.0-rc.3 - FlandreDevs,bsdayo - Konata.Core (QQ Protocol) adapter for Flandre project. - bot;chatbot;flandre;adapter;konata - GPL-3.0-only - avatar.jpg - README.md - - net6.0 - enable - enable - Library - true - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2022-2023 - - - - - - - - - - - - - - - - diff --git a/src/Flandre.Adapters.Konata/KonataAdapter.cs b/src/Flandre.Adapters.Konata/KonataAdapter.cs deleted file mode 100644 index 34be846..0000000 --- a/src/Flandre.Adapters.Konata/KonataAdapter.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Flandre.Core.Common; - -namespace Flandre.Adapters.Konata; - -/// -/// Konata 适配器 -/// -public class KonataAdapter : IAdapter -{ - /// - public IEnumerable Bots => _bots.AsReadOnly(); - - private readonly List _bots = new(); - private readonly KonataAdapterConfig _config; - - /// - /// 构造适配器实例 - /// - /// 适配器配置 - public KonataAdapter(KonataAdapterConfig config) - { - _config = config; - - _config.Bots.ForEach(bot => - _bots.Add(new KonataBot(bot))); - } - - /// - /// 启动适配器 - /// - public Task StartAsync() => Task.WhenAll(_bots.ConvertAll(bot => bot.StartAsync())); - - /// - /// 停止适配器 - /// - public Task StopAsync() => Task.WhenAll(_bots.ConvertAll(bot => bot.StopAsync())); -} - -/// -/// Konata 适配器配置 -/// -public sealed class KonataAdapterConfig -{ - /// - /// 构造 Konata 适配器配置 - /// - public KonataAdapterConfig() - { - Bots = new List(); - } - - /// - /// 构造 Konata 适配器配置,并使用已有的 bot 配置列表 - /// - /// - public KonataAdapterConfig(List bots) - { - Bots = bots; - } - - /// - /// bot 配置列表 - /// - public List Bots { get; init; } -} diff --git a/src/Flandre.Adapters.Konata/KonataBot.cs b/src/Flandre.Adapters.Konata/KonataBot.cs deleted file mode 100644 index 7f7526f..0000000 --- a/src/Flandre.Adapters.Konata/KonataBot.cs +++ /dev/null @@ -1,376 +0,0 @@ -using Flandre.Core.Common; -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Core.Models; -using Konata.Core.Common; -using Konata.Core.Events; -using Konata.Core.Events.Model; -using Konata.Core.Interfaces; -using Konata.Core.Interfaces.Api; -using BotConfig = Konata.Core.Common.BotConfig; -using FlandreBotConfig = Flandre.Core.Common.BotConfig; -using FlandreBot = Flandre.Core.Common.Bot; -using KonataInternalBot = Konata.Core.Bot; - -namespace Flandre.Adapters.Konata; - -/// -/// Konata Bot -/// -public sealed class KonataBot : FlandreBot -{ - /// - /// Bot 平台名称,值为 konata - /// - public override string Platform => _config.PlatformOverride ?? "konata"; - - /// - public override string SelfId { get; } - - /// - /// Konata 内部 bot - /// - public KonataInternalBot Internal { get; } - - private readonly KonataBotConfig _config; - - /// - public override event BotEventHandler? MessageReceived; - - /// - public override event BotEventHandler? GuildInvited; - - /// - public override event BotEventHandler? GuildJoinRequested; - - /// - public override event BotEventHandler? FriendRequested; - - internal KonataBot(KonataBotConfig config) - { - Internal = BotFather.Create(config.Konata, config.Device, config.KeyStore); - _config = config; - - SelfId = string.IsNullOrWhiteSpace(_config.SelfId) - ? Internal.Uin.ToString() - : _config.SelfId; - - Internal.OnFriendMessage += InternalOnFriendMessage; - Internal.OnGroupMessage += InternalOnGroupMessage; - Internal.OnGroupInvite += InternalOnGroupInvite; - Internal.OnGroupRequestJoin += InternalOnGroupRequestJoin; - Internal.OnFriendRequest += InternalOnFriendRequest; - - Internal.OnCaptcha += InternalOnCaptcha; - Internal.OnLog += InternalOnLog; - } - - #region 生命周期 - - /// - public override async Task StartAsync() - { - Log(BotLogLevel.Debug, $"Trying to log in bot {_config.KeyStore.Account.Uin}..."); - if (!await Internal.Login()) - { - Log(BotLogLevel.Warning, $"Bot {_config.KeyStore.Account.Uin} login failed."); - return; - } - - _config.KeyStore = Internal.KeyStore; - Log(BotLogLevel.Information, $"Bot {_config.KeyStore.Account.Uin} started."); - } - - /// - public override Task StopAsync() => Task.Run(() => Internal.Dispose()); - - #endregion 生命周期 - - #region 消息相关 - - /// - /// 发送群消息 - /// - /// 群号 - /// 消息内容 - /// 群号,可不提供 - public override async Task SendChannelMessageAsync(string channelId, MessageContent content, - string? guildId = null) - { - await Internal.SendGroupMessage( - uint.Parse(channelId), content.ToKonataMessageChain()); - return null; - } - - /// - /// 发送私聊消息 - /// - /// 用户 QQ 号 - /// 消息内容 - public override async Task SendPrivateMessageAsync(string userId, MessageContent content) - { - await Internal.SendFriendMessage( - uint.Parse(userId), content.ToKonataMessageChain()); - return null; - } - - #endregion 消息相关 - - #region 用户相关 - - /// - public override Task GetSelfAsync() - { - return Task.FromResult(new User - { - Name = Internal.Name, - UserId = Internal.Uin.ToString(), - AvatarUrl = CommonUtils.GetAvatarUrl(Internal.Uin) - }); - } - - /// - /// 在 Konata 中只能获取好友的信息。 - public override async Task GetUserAsync(string userId, string? guildId = null) - { - return (await GetFriendListAsync()).FirstOrDefault(user => user.UserId == userId); - } - - /// - public override async Task> GetFriendListAsync() - { - return (await Internal.GetFriendList(true)).Select(friend => new User - { - Name = friend.Name, - Nickname = friend.Remark, - UserId = friend.Uin.ToString(), - AvatarUrl = CommonUtils.GetAvatarUrl(friend.Uin) - }); - } - - #endregion 用户相关 - - #region Guild 相关 - - /// - /// 获取群信息 - /// - /// 群号 - public override async Task GetGuildAsync(string guildId) - { - return (await GetGuildListAsync()).FirstOrDefault(guild => guild.Id == guildId); - } - - /// - /// 获取 Bot 加入的群列表 - /// - public override async Task> GetGuildListAsync() - { - return (await Internal.GetGroupList(true)) - .Select(group => new Guild - { - Id = group.Uin.ToString(), - Name = group.Name - }); - } - - /// - /// 获取群成员信息 - /// - /// 群号 - /// 群成员 QQ - public override async Task GetGuildMemberAsync(string guildId, string userId) - { - return (await GetGuildMemberListAsync(guildId)).FirstOrDefault(member => member.UserId == userId); - } - - /// - /// 获取群成员列表 - /// - /// 群号 - public override async Task> GetGuildMemberListAsync(string guildId) - { - return (await Internal.GetGroupMemberList(uint.Parse(guildId), true)) - .Select(member => new GuildMember - { - Name = member.Name, - Nickname = member.NickName, - UserId = member.Uin.ToString(), - AvatarUrl = CommonUtils.GetAvatarUrl(member.Uin), - Roles = new List { member.Role.ToString() } - }); - } - - #endregion Guild 相关 - - #region Channel 相关 - - /// - /// 获取群列表,对于 Konata 等效于 - /// - public override async Task GetChannelAsync(string channelId, string? guildId = null) - { - return (await GetChannelListAsync("")).FirstOrDefault(channel => channel.Id == channelId); - } - - /// - /// 获取群列表,对于 Konata 等效于 - /// - public override async Task> GetChannelListAsync(string guildId) - { - return (await Internal.GetGroupList(true)) - .Select(group => new Channel - { - Id = group.Uin.ToString(), - Name = group.Name - }); - } - - #endregion Channel 相关 - - #region 内部事件 - - private void InternalOnFriendMessage(KonataInternalBot bot, FriendMessageEvent e) - { - MessageReceived?.Invoke(this, new BotMessageReceivedEvent(e.Message.ToFlandreMessage(Platform))); - } - - private void InternalOnGroupMessage(KonataInternalBot bot, GroupMessageEvent e) - { - MessageReceived?.Invoke(this, new BotMessageReceivedEvent(e.Message.ToFlandreMessage(Platform))); - } - - private void InternalOnGroupInvite(KonataInternalBot bot, GroupInviteEvent e) - { - GuildInvited?.Invoke(this, new BotGuildInvitedEvent( - e.GroupName, e.GroupUin.ToString(), - e.InviterNick, e.InviterUin.ToString(), e.InviterIsAdmin) { EventPayload = e.Token }); - } - - private void InternalOnGroupRequestJoin(KonataInternalBot bot, GroupRequestJoinEvent e) - { - GuildJoinRequested?.Invoke(this, new BotGuildJoinRequestedEvent( - e.GroupName, e.GroupUin.ToString(), - e.ReqNick, e.ReqUin.ToString(), e.ReqComment) { EventPayload = e.Token }); - } - - private void InternalOnFriendRequest(KonataInternalBot bot, FriendRequestEvent e) - { - FriendRequested?.Invoke(this, new BotFriendRequestedEvent( - e.ReqNick, e.ReqUin.ToString(), e.ReqComment) { EventPayload = e.Token }); - } - - private void InternalOnCaptcha(KonataInternalBot bot, CaptchaEvent e) - { - Log(BotLogLevel.Warning, $"Bot {_config.SelfId} needs login verification."); - - switch (e.Type) - { - case CaptchaEvent.CaptchaType.Sms: - Log(BotLogLevel.Warning, - $"The phone verify code has been sent to {e.Phone}. Please input the code and press Enter."); - Console.Write("Phone verify code: "); - Internal.SubmitSmsCode(Console.ReadLine()); - break; - - case CaptchaEvent.CaptchaType.Slider: - Log(BotLogLevel.Warning, - $"The Slider Captcha URL is: {e.SliderUrl}. Please input the ticket and press Enter."); - Console.Write("Ticket: "); - Internal.SubmitSliderTicket(Console.ReadLine()); - break; - } - } - - private void InternalOnLog(KonataInternalBot bot, LogEvent e) - { - switch (e.Level) - { - case LogLevel.Verbose: - Log(BotLogLevel.Trace, e.EventMessage); - break; - - case LogLevel.Information: - Log(BotLogLevel.Information, e.EventMessage); - break; - - case LogLevel.Warning: - Log(BotLogLevel.Warning, e.EventMessage); - break; - - case LogLevel.Exception: - Log(BotLogLevel.Error, e.EventMessage); - break; - - case LogLevel.Fatal: - Log(BotLogLevel.Critical, e.EventMessage); - break; - } - } - - #endregion 内部事件 - - #region 事件处理 - - /// - public override async Task HandleGuildInvitationAsync(BotGuildInvitedEvent e, bool approve, string? comment = null) - { - if (approve) - await Internal.ApproveGroupInvitation( - uint.Parse(e.GuildId), uint.Parse(e.InviterId), (long)e.EventPayload!); - else - await Internal.DeclineGroupInvitation( - uint.Parse(e.GuildId), uint.Parse(e.InviterId), (long)e.EventPayload!, comment ?? ""); - } - - /// - public override async Task HandleGuildJoinRequestAsync(BotGuildJoinRequestedEvent e, bool approve, - string? comment = null) - { - if (approve) - await Internal.ApproveGroupRequestJoin( - uint.Parse(e.GuildId), uint.Parse(e.RequesterId), (long)e.EventPayload!); - else - await Internal.DeclineGroupRequestJoin( - uint.Parse(e.GuildId), uint.Parse(e.RequesterId), (long)e.EventPayload!, comment ?? ""); - } - - /// - public override async Task HandleFriendRequestAsync(BotFriendRequestedEvent e, bool approve, string? comment = null) - { - if (approve) - await Internal.ApproveFriendRequest( - uint.Parse(e.RequesterId), (long)e.EventPayload!); - else - await Internal.DeclineFriendRequest( - uint.Parse(e.RequesterId), (long)e.EventPayload!); - } - - #endregion -} - -/// -/// Konata Bot 配置 -/// -public class KonataBotConfig : FlandreBotConfig -{ - /// - /// Konata 内部 Bot 配置 - /// - public BotConfig Konata { get; set; } = BotConfig.Default(); - - /// - /// Konata 设备信息 - /// - public BotDevice Device { get; set; } = BotDevice.Default(); - - /// - /// Konata 密钥信息 - /// - public BotKeyStore KeyStore { get; set; } = new(); - - /// - /// 覆盖平台字符串。对于使用多个 QQ 适配器时很有用,例如设置为 onebot。 - /// - public string? PlatformOverride { get; set; } = null; -} diff --git a/src/Flandre.Adapters.Konata/LICENSE b/src/Flandre.Adapters.Konata/LICENSE deleted file mode 100644 index f288702..0000000 --- a/src/Flandre.Adapters.Konata/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/src/Flandre.Adapters.Konata/README.md b/src/Flandre.Adapters.Konata/README.md deleted file mode 100644 index 9a478e7..0000000 --- a/src/Flandre.Adapters.Konata/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Flandre.Adapters.Konata - -基于 [Konata.Core](https://github.com/KonataDev/Konata.Core) 实现的 QQ 协议适配器 - -[![NuGet](https://img.shields.io/nuget/vpre/Flandre.Adapters.Konata?label=NuGet&color=blue)](https://www.nuget.org/packages/Flandre.Adapters.Konata/) -[![NuGet Downloads](https://img.shields.io/nuget/dt/Flandre.Adapters.Konata?label=Downloads&color=f06292)](https://www.nuget.org/packages/Flandre.Adapters.Konata/) - -## 小贴士 - -- 由于 Konata 机制,在接收到的消息中,图片消息段 (`ImageSegment`) 将固定只包含 `Url` 属性,为图片的链接,需要自行下载。 -- ~~想到什么再补~~ - -## 开源许可 -由于 GPL v3 协议的传染性,不同于 Flandre 项目其他部分,Flandre.Adapters.Konata 采用 [GPL v3](./LICENSE) 协议开源。 diff --git a/src/Flandre.Adapters.Konata/Utils.cs b/src/Flandre.Adapters.Konata/Utils.cs deleted file mode 100644 index 932ff09..0000000 --- a/src/Flandre.Adapters.Konata/Utils.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Flandre.Core.Messaging; -using Flandre.Core.Messaging.Segments; -using Flandre.Core.Models; -using Konata.Core.Message; -using Konata.Core.Message.Model; -using FlandreMessageBuilder = Flandre.Core.Messaging.MessageBuilder; -using KonataMessageBuilder = Konata.Core.Message.MessageBuilder; - -namespace Flandre.Adapters.Konata; - -internal static class CommonUtils -{ - public static string GetAvatarUrl(uint userId) - { - return $"http://q.qlogo.cn/headimg_dl?dst_uin={userId}&spec=640"; - } -} - -internal static class MessageUtils -{ - internal static Message ToFlandreMessage(this MessageStruct message, string platform) - { - var mb = new FlandreMessageBuilder(); - - foreach (var chain in message.Chain) - switch (chain) - { - case TextChain textChain: - mb.Text(textChain.Content); - break; - - case ImageChain imageChain: - mb.Image(ImageSegment.FromUrl(imageChain.ImageUrl)); - break; - } - - var groupId = message.Type == MessageStruct.SourceType.Group - ? message.Receiver.Uin.ToString() - : null; - - return new Message - { - Time = DateTimeOffset.FromUnixTimeSeconds(message.Time).LocalDateTime, - Platform = platform, - Environment = message.Type == MessageStruct.SourceType.Group - ? MessageEnvironment.Channel - : MessageEnvironment.Private, - MessageId = message.Uuid.ToString(), - GuildId = groupId, - ChannelId = groupId, - Sender = new User - { - Name = message.Sender.Name, - // Nickname = message.Sender.Name, (can't get user's nickname) - UserId = message.Sender.Uin.ToString(), - AvatarUrl = CommonUtils.GetAvatarUrl(message.Sender.Uin) - }, - Content = mb.Build() - }; - } - - internal static MessageChain ToKonataMessageChain(this MessageContent content) - { - var mb = new KonataMessageBuilder(); - - var prefixChecked = false; - foreach (var segment in content) - switch (segment) - { - case QuoteSegment quoteSegment: - if (!prefixChecked) - { - var messageStruct = new MessageStruct( - uint.Parse(quoteSegment.QuotedMessage.Sender.UserId), - quoteSegment.QuotedMessage.Sender.Nickname, - quoteSegment.QuotedMessage.Content.ToKonataMessageChain(), - quoteSegment.QuotedMessage.Environment == - MessageEnvironment.Channel - ? MessageStruct.SourceType.Group - : MessageStruct.SourceType.Friend); - mb.Add(ReplyChain.Create(messageStruct)); - prefixChecked = true; - } - - break; - - case TextSegment textSegment: - mb.Text(textSegment.Text); - break; - - case ImageSegment imageSegment: - if (imageSegment.Path is not null) - mb.Image(imageSegment.Path); - else if (imageSegment.Data is not null) - mb.Image(imageSegment.Data); - else if (imageSegment.Url is not null) - mb.Add(ImageChain.CreateFromUrl(imageSegment.Url)); - break; - } - - return mb.Build(); - } -} diff --git a/src/Flandre.Adapters.Mock/Extensions.cs b/src/Flandre.Adapters.Mock/Extensions.cs deleted file mode 100644 index 4bbd6b1..0000000 --- a/src/Flandre.Adapters.Mock/Extensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Flandre.Core.Messaging; - -namespace Flandre.Adapters.Mock; - -public static class MockAdapterExtensions -{ - public static MockClient GetChannelClient(this MockAdapter adapter, string guildId, string channelId, - string userId) - { - return new MockClient(adapter) - { - EnvironmentType = MessageEnvironment.Channel, - GuildId = guildId, - ChannelId = channelId, - UserId = userId - }; - } - - public static MockClient GetChannelClient(this MockAdapter adapter) - { - return GetChannelClient(adapter, Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), - Guid.NewGuid().ToString()); - } - - public static MockClient GetFriendClient(this MockAdapter adapter, string userId) - { - return new MockClient(adapter) - { - EnvironmentType = MessageEnvironment.Private, - UserId = userId - }; - } - - public static MockClient GetFriendClient(this MockAdapter adapter) - { - return GetFriendClient(adapter, Guid.NewGuid().ToString()); - } -} diff --git a/src/Flandre.Adapters.Mock/Flandre.Adapters.Mock.csproj b/src/Flandre.Adapters.Mock/Flandre.Adapters.Mock.csproj deleted file mode 100644 index 61ca077..0000000 --- a/src/Flandre.Adapters.Mock/Flandre.Adapters.Mock.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - Flandre.Adapters.Mock - 1.0.0-rc.4 - FlandreDevs,bsdayo - Mock client for Project Flandre. - bot;chatbot;flandre;adapter - MIT - avatar.jpg - - net6.0 - enable - enable - Library - true - CS1591 - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2022-2023 - - - - - - - - - - - diff --git a/src/Flandre.Adapters.Mock/MockAdapter.cs b/src/Flandre.Adapters.Mock/MockAdapter.cs deleted file mode 100644 index 6e8114e..0000000 --- a/src/Flandre.Adapters.Mock/MockAdapter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Flandre.Core.Common; - -#pragma warning disable CS1998 - -namespace Flandre.Adapters.Mock; - -public class MockAdapter : IAdapter -{ - public IEnumerable Bots => new[] { Bot }; - - internal readonly MockBot Bot = new(); - - public Task StartAsync() => Task.CompletedTask; - - public Task StopAsync() => Task.CompletedTask; -} diff --git a/src/Flandre.Adapters.Mock/MockBot.cs b/src/Flandre.Adapters.Mock/MockBot.cs deleted file mode 100644 index a987e17..0000000 --- a/src/Flandre.Adapters.Mock/MockBot.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Flandre.Core.Common; -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Core.Models; - -#pragma warning disable CS1998 - -namespace Flandre.Adapters.Mock; - -public class MockBot : Bot -{ - /// - /// Bot 平台名称,值为 mock - /// - public override string Platform => "mock"; - - public override string SelfId => _selfId; - - private readonly string _selfId = Guid.NewGuid().ToString(); - - internal (string MessageId, TaskCompletionSource Tcs)? ReplyTarget { get; set; } - - internal void ReceiveMessage(Message message) - { - MessageReceived?.Invoke(this, new BotMessageReceivedEvent(message)); - } - - private void Send(MessageContent? content) - { - ReplyTarget?.Tcs.TrySetResult(content); - } - - public override async Task SendChannelMessageAsync(string channelId, MessageContent content, - string? guildId = null) - { - Send(content); - return null; - } - - public override async Task SendPrivateMessageAsync(string userId, MessageContent content) - { - Send(content); - return null; - } - - public override async Task GetSelfAsync() - { - return new User - { - Name = "Test Bot", - Nickname = "Test Bot", - UserId = _selfId - }; - } - - public override event BotEventHandler? MessageReceived; - public override event BotEventHandler? GuildInvited; - public override event BotEventHandler? GuildJoinRequested; - public override event BotEventHandler? FriendRequested; -} diff --git a/src/Flandre.Adapters.Mock/MockClient.cs b/src/Flandre.Adapters.Mock/MockClient.cs deleted file mode 100644 index 22eab65..0000000 --- a/src/Flandre.Adapters.Mock/MockClient.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Flandre.Core.Messaging; -using Flandre.Core.Models; - -namespace Flandre.Adapters.Mock; - -public class MockClient -{ - private readonly MockAdapter _adapter; - - public string GuildId { get; internal init; } = string.Empty; - public string ChannelId { get; internal init; } = string.Empty; - public string UserId { get; internal init; } = string.Empty; - - public MessageEnvironment EnvironmentType { get; internal init; } - - internal MockClient(MockAdapter adapter) - { - _adapter = adapter; - } - - private Message ConstructMessage(string message) - { - return new Message - { - Time = DateTime.Now, - Environment = EnvironmentType, - MessageId = Guid.NewGuid().ToString(), - GuildId = GuildId, - ChannelId = ChannelId, - Sender = new GuildMember - { - Name = "Test Client", - Nickname = "Test Client", - UserId = UserId, - AvatarUrl = null, - Roles = new List() - }, - Content = message - }; - } - - public void SendMessage(string message) - { - var msg = ConstructMessage(message); - _adapter.Bot.ReceiveMessage(msg); - } - - public Task SendMessageForReplyAsync(string message) - { - return SendMessageForReplyAsync(message, TimeSpan.FromSeconds(10)); - } - - public Task SendMessageForReplyAsync(string message, TimeSpan timeout) - { - var tcs = new TaskCompletionSource(); - - var msg = ConstructMessage(message); - - _adapter.Bot.ReplyTarget = (msg.MessageId, tcs); - _adapter.Bot.ReceiveMessage(msg); - - Task.Run(async () => - { - await Task.Delay(timeout); - if (_adapter.Bot.ReplyTarget is { } target - && target.MessageId == msg.MessageId - && !target.Tcs.Task.IsCompleted) - { - _adapter.Bot.ReplyTarget = null; - tcs.TrySetResult(null); - } - }); - - return tcs.Task; - } -} diff --git a/src/Flandre.Adapters.Mock/MockClientExtensions.cs b/src/Flandre.Adapters.Mock/MockClientExtensions.cs deleted file mode 100644 index 69534be..0000000 --- a/src/Flandre.Adapters.Mock/MockClientExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Flandre.Core.Messaging; - -namespace Flandre.Adapters.Mock; - -public static class MockClientExtensions -{ - public static MessageContent? SendMessageForReply(this MockClient client, string message) - { - return client.SendMessageForReplyAsync(message).GetAwaiter().GetResult(); - } - - public static MessageContent? SendMessageForReply(this MockClient client, string message, TimeSpan timeout) - { - return client.SendMessageForReplyAsync(message, timeout).GetAwaiter().GetResult(); - } -} diff --git a/src/Flandre.Adapters.OneBot.Extensions/AdapterCollectionExtensions.cs b/src/Flandre.Adapters.OneBot.Extensions/AdapterCollectionExtensions.cs deleted file mode 100644 index f2118c4..0000000 --- a/src/Flandre.Adapters.OneBot.Extensions/AdapterCollectionExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Flandre.Framework; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Flandre.Adapters.OneBot.Extensions; - -/// -/// OneBot 适配器扩展 -/// -public static class AdapterCollectionExtensions -{ - /// - /// 添加 OneBot 适配器,自动从配置根中的 Adapters:OneBot 项读取配置。 - /// - public static void AddOneBot(this IAdapterCollection adapters) - { - var config = adapters.Services - .BuildServiceProvider() - .GetRequiredService() - .GetSection("Adapters:OneBot") - .Get(); - adapters.Add(new OneBotAdapter(config ?? new OneBotAdapterConfig())); - } - - /// - /// 添加 OneBot 适配器。 - /// - public static void AddOneBot(this IAdapterCollection adapters, IConfiguration configuration) - { - var config = configuration.Get(); - adapters.Add(new OneBotAdapter(config ?? new OneBotAdapterConfig())); - } - - /// - /// 添加 OneBot 适配器。 - /// - public static void AddOneBot(this IAdapterCollection adapters, Action action) - { - var config = new OneBotAdapterConfig(); - action(config); - adapters.Add(new OneBotAdapter(config)); - } - - /// - /// 添加 OneBot 适配器。 - /// - public static void AddOneBot(this IAdapterCollection adapters, OneBotAdapterConfig config) - { - adapters.Add(new OneBotAdapter(config)); - } -} diff --git a/src/Flandre.Adapters.OneBot.Extensions/Flandre.Adapters.OneBot.Extensions.csproj b/src/Flandre.Adapters.OneBot.Extensions/Flandre.Adapters.OneBot.Extensions.csproj deleted file mode 100644 index 99fd693..0000000 --- a/src/Flandre.Adapters.OneBot.Extensions/Flandre.Adapters.OneBot.Extensions.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - Flandre.Adapters.OneBot.Extensions - 2.0.0-rc.3 - FlandreDevs,bsdayo - Flandre.Framework extensions for Flandre.Adapters.OneBot. - bot;chatbot;flandre;adapter;onebot;extensions - MIT - avatar.jpg - - net6.0 - enable - enable - Library - true - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2022-2023 - - - - - - - - - - - - diff --git a/src/Flandre.Adapters.OneBot/CqCodeParser.cs b/src/Flandre.Adapters.OneBot/CqCodeParser.cs deleted file mode 100644 index 2847e66..0000000 --- a/src/Flandre.Adapters.OneBot/CqCodeParser.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System.Text; -using Flandre.Adapters.OneBot.Segments; -using Flandre.Core.Messaging; -using Flandre.Core.Messaging.Segments; -using Flandre.Core.Utils; - -namespace Flandre.Adapters.OneBot; - -public static class CqCodeParser -{ - /// - /// 将含有 CQ 码的消息解析为 。 - /// - /// 含有 CQ 码的消息 - public static MessageContent ParseCqMessage(string message) - { - if (string.IsNullOrEmpty(message)) - return ""; - - var parser = new StringParser(message); - var segments = new List(); - - while (!parser.IsEnd) - if (parser.Current == '[') - { - // CQCode - var code = parser.Read(']', true); - var sections = code[4..^1].Split(','); - segments.Add(sections[0] switch - { - "face" => ParseFace(sections[1..]), - "record" => ParseRecord(sections[1..]), - "image" => ParseImage(sections[1..]), - "at" => ParseAt(sections[1..]), - _ => new TextSegment(code) - }); - } - else - { - var text = parser.Read('['); - segments.Add(new TextSegment(OneBotUtils.UnescapeCqCode(text))); - } - - return new MessageContent(segments); - } - - public static string ToCqMessage(this MessageContent content) - { - var sb = new StringBuilder(); - foreach (var segment in content) - sb.Append(segment.ToCqCode()); - return sb.ToString(); - } - - public static string ToCqCode(this MessageSegment segment) - { - switch (segment) - { - case TextSegment ts: - return OneBotUtils.EscapeCqCode(ts.Text); - - case FaceSegment fs: - return $"[CQ:face,id={fs.FaceId}]"; - - case AudioSegment aus: - if (aus.Data is not null) - return $"[CQ:record,file=base64://{Convert.ToBase64String(aus.Data)}]"; - if (aus.Path is not null) - return $"[CQ:record,file={aus.Path}]"; - if (aus.Url is not null) - return $"[CQ:record,file={aus.Url}]"; - break; - - case ImageSegment ims: - var type = ims.Type is null ? "" : $",type={ims.Type}"; - if (ims.Data is not null) - return $"[CQ:image,file=base64://{Convert.ToBase64String(ims.Data)}{type}]"; - if (ims.Path is not null) - return $"[CQ:image,file={ims.Path}{type}]"; - if (ims.Url is not null) - return $"[CQ:image,file={ims.Url}{type}]"; - break; - - case QuoteSegment qs: - return $"[CQ:reply,id={qs.QuotedMessage.MessageId}]"; - - case AtSegment ats: - return $"[CQ:at,qq={(ats.Scope == AtSegmentScope.All ? "all" : ats.UserId)}]"; - } - - return ""; - } - - private static FaceSegment ParseFace(string[] data) - { - return new FaceSegment(data[0][3..]); // id=xxx - } - - private static OneBotRecordSegment ParseRecord(string[] data) - { - var segment = new OneBotRecordSegment(); - foreach (var d in data) - { - var kv = d.Split('='); - switch (kv[0]) - { - case "file": - segment.Filename = kv[1]; - break; - case "url": - segment.Url = string.Join('=', kv[1..]); - break; - case "magic": - segment.Magic = int.Parse(kv[1]); - break; - } - } - - return segment; - } - - private static OneBotImageSegment ParseImage(string[] data) - { - var segment = new OneBotImageSegment(); - foreach (var d in data) - { - var kv = d.Split('='); - switch (kv[0]) - { - case "file": - segment.Filename = kv[1]; - break; - case "type": - segment.Type = kv[1]; - break; - case "subType": - segment.SubType = kv[1]; - break; - case "url": - segment.Url = string.Join('=', kv[1..]); - break; - case "id": - segment.Id = int.Parse(kv[1]); - break; - } - } - - return segment; - } - - private static AtSegment ParseAt(string[] data) - { - return new AtSegment(data[0][3..]); // qq=xxx - } -} diff --git a/src/Flandre.Adapters.OneBot/Flandre.Adapters.OneBot.csproj b/src/Flandre.Adapters.OneBot/Flandre.Adapters.OneBot.csproj deleted file mode 100644 index d3ad037..0000000 --- a/src/Flandre.Adapters.OneBot/Flandre.Adapters.OneBot.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - Flandre.Adapters.OneBot - 2.0.0-rc.4 - FlandreDevs,bsdayo - OneBot protocol adapter for Flandre project. - bot;chatbot;flandre;adapter;onebot - MIT - avatar.jpg - README.md - - net6.0 - enable - enable - Library - true - 1591 - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2022-2023 - - - - - - - - - - - - - - - - diff --git a/src/Flandre.Adapters.OneBot/GuildBot.cs b/src/Flandre.Adapters.OneBot/GuildBot.cs deleted file mode 100644 index 7d8f8ea..0000000 --- a/src/Flandre.Adapters.OneBot/GuildBot.cs +++ /dev/null @@ -1,169 +0,0 @@ -using Flandre.Adapters.OneBot.Models; -using Flandre.Core.Common; -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Core.Models; - -#pragma warning disable CS0067 - -namespace Flandre.Adapters.OneBot; - -public class OneBotGuildBot : Bot -{ - /// - /// Bot 平台名称,值为 qqguild - /// - public override string Platform => "qqguild"; - - public override string SelfId => _selfId; - - private string _selfId = string.Empty; - private bool _isSelfIdSet; - - public OneBotGuildInternalBot Internal { get; } - - public override event BotEventHandler? MessageReceived; - public override event BotEventHandler? GuildInvited; - public override event BotEventHandler? GuildJoinRequested; - public override event BotEventHandler? FriendRequested; - - internal OneBotGuildBot(OneBotBot mainBot) - { - Internal = new OneBotGuildInternalBot(mainBot); - } - - internal void InvokeMessageEvent(OneBotApiGuildMessageEvent e) - { - if (!_isSelfIdSet) - { - _selfId = e.SelfId.ToString(); - _isSelfIdSet = true; - } - - MessageReceived?.Invoke(this, - new BotMessageReceivedEvent(new Message - { - Time = DateTimeOffset.FromUnixTimeSeconds(e.Time).DateTime, - Platform = Platform, - Environment = MessageEnvironment.Channel, - MessageId = e.MessageId!, - GuildId = e.GuildId, - ChannelId = e.ChannelId, - Sender = new User - { - Name = e.Sender.Nickname, - UserId = e.Sender.TinyId! - }, - Content = CqCodeParser.ParseCqMessage(e.Message!) - })); - } - - public override async Task SendChannelMessageAsync(string channelId, MessageContent content, - string? guildId = null) - { - return await Internal.SendGuildChannelMessage(guildId!, channelId, content); - } - - public override async Task GetSelfAsync() - { - var self = await Internal.GetGuildServiceProfile(); - return new User - { - Name = self.Nickname!, - UserId = self.TinyId!, - AvatarUrl = self.AvatarUrl - }; - } - - public override Task GetUserAsync(string userId, string? guildId = null) - { - Log(BotLogLevel.Warning, - $"Platform qqguild does not support method {nameof(GetUserAsync)}. If you need to get the information of guild member, please use method {nameof(GetGuildMemberAsync)} instead."); - return Task.FromResult(null); - } - - public override Task> GetFriendListAsync() - { - return Task.FromResult>(Array.Empty()); - } - - public override async Task GetGuildAsync(string guildId) - { - try - { - var guild = await Internal.GetGuildMetaByGuest(guildId); - return new Guild - { - Id = guild.GuildId!, - Name = guild.GuildName! - }; - } - catch - { - return null; - } - } - - public override async Task> GetGuildListAsync() - { - return (await Internal.GetGuildList()).Select(g => new Guild - { - Id = g.GuildId!, - Name = g.GuildName! - }); - } - - public override async Task GetGuildMemberAsync(string guildId, string userId) - { - try - { - var user = await Internal.GetGuildMemberProfile(guildId, userId); - return new GuildMember - { - Name = user.Nickname!, - UserId = user.TinyId!, - AvatarUrl = user.AvatarUrl, - Roles = user.Roles?.Select(r => r.RoleName!).ToList() ?? new List() - }; - } - catch - { - return null; - } - } - - public override async Task> GetGuildMemberListAsync(string guildId) - { - var list = new List(); - OneBotGuildMemberListResponse resp; - var nextToken = ""; - - do - { - resp = await Internal.GetGuildMemberList(guildId, nextToken); - nextToken = resp.NextToken!; - list.AddRange(resp.Members!); - } while (!resp.Finished); - - return list.Select(m => new GuildMember - { - Name = m.Nickname!, - UserId = m.TinyId!, - Roles = new List { m.RoleName! } - }); - } - - public override async Task GetChannelAsync(string channelId, string? guildId = null) - { - return (await GetChannelListAsync(guildId!)).FirstOrDefault(c => c.Id == channelId); - } - - public override async Task> GetChannelListAsync(string guildId) - { - return (await Internal.GetGuildChannelList(guildId)).Select(c => new Channel - { - Id = c.ChannelId!, - Name = c.ChannelName! - }); - } -} diff --git a/src/Flandre.Adapters.OneBot/GuildInternalBot.cs b/src/Flandre.Adapters.OneBot/GuildInternalBot.cs deleted file mode 100644 index 74ed93c..0000000 --- a/src/Flandre.Adapters.OneBot/GuildInternalBot.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Text.Json; -using Flandre.Adapters.OneBot.Models; -using Flandre.Core.Messaging; - -namespace Flandre.Adapters.OneBot; - -public class OneBotGuildInternalBot -{ - private readonly OneBotBot _mainBot; - - internal OneBotGuildInternalBot(OneBotBot mainBot) - { - _mainBot = mainBot; - } - - public async Task GetGuildServiceProfile() - { - return (await _mainBot.SendApiRequest("get_guild_service_profile")) - .Deserialize()!; - } - - public async Task GetGuildList() - { - var list = await _mainBot.SendApiRequest("get_guild_list"); - return list.ValueKind == JsonValueKind.Null - ? Array.Empty() - : list.Deserialize()!; - } - - public async Task GetGuildMetaByGuest(string guildId) - { - return (await _mainBot.SendApiRequest("get_guild_service_profile", - new { guild_id = guildId })) - .Deserialize()!; - } - - public async Task GetGuildChannelList(string guildId, bool noCache = false) - { - return (await _mainBot.SendApiRequest("get_guild_channel_list", new - { - guild_id = guildId, - no_cache = noCache - })) - .Deserialize()!; - } - - public async Task GetGuildMemberList(string guildId, string nextToken = "") - { - return (await _mainBot.SendApiRequest("get_guild_member_list", new - { - guild_id = guildId, - next_token = nextToken - })) - .Deserialize()!; - } - - public async Task GetGuildMemberProfile(string guildId, string userId) - { - return (await _mainBot.SendApiRequest("get_guild_member_profile", new - { - guild_id = guildId, - user_id = userId - })) - .Deserialize()!; - } - - public async Task SendGuildChannelMessage(string guildId, string channelId, string message) - { - return (await _mainBot.SendApiRequest("send_guild_channel_msg", new - { - guild_id = guildId, - channel_id = channelId, - message - })) - .GetProperty("message_id").ToString(); - } - - public Task SendGuildChannelMessage(string guildId, string channelId, MessageContent content) - { - return SendGuildChannelMessage(guildId, channelId, content.ToCqMessage()); - } -} diff --git a/src/Flandre.Adapters.OneBot/InternalBot.cs b/src/Flandre.Adapters.OneBot/InternalBot.cs deleted file mode 100644 index a98f0d0..0000000 --- a/src/Flandre.Adapters.OneBot/InternalBot.cs +++ /dev/null @@ -1,269 +0,0 @@ -using System.Text.Json; -using Flandre.Adapters.OneBot.Models; -using Flandre.Core.Messaging; - -// ReSharper disable RedundantAnonymousTypePropertyName - -namespace Flandre.Adapters.OneBot; - -public class OneBotInternalBot -{ - private readonly OneBotBot _parent; - - internal OneBotInternalBot(OneBotBot parent) - { - _parent = parent; - } - - public async Task SendPrivateMessage(long userId, string message, bool autoEscape = false) - { - var resp = await _parent.SendApiRequest("send_private_msg", - new - { - user_id = userId, - message = message, - auto_escape = autoEscape - }); - return resp.GetProperty("message_id").GetInt32(); - } - - public Task SendPrivateMessage(long userId, MessageContent content, bool autoEscape = false) - { - return SendPrivateMessage(userId, content.ToCqMessage(), autoEscape); - } - - public async Task SendGroupMessage(long groupId, string message, bool autoEscape = false) - { - var resp = await _parent.SendApiRequest("send_group_msg", - new - { - group_id = groupId, - message = message, - auto_escape = autoEscape - }); - return resp.GetProperty("message_id").GetInt32(); - } - - public Task SendGroupMessage(long groupId, MessageContent content, bool autoEscape = false) - { - return SendGroupMessage(groupId, content.ToCqMessage(), autoEscape); - } - - public async Task SendMessage(long? userId, long? groupId, string message, bool autoEscape = false) - { - var resp = await _parent.SendApiRequest("send_msg", - new - { - message_type = userId is not null ? "private" : "group", - user_id = userId, - group_id = groupId, - message = message, - auto_escape = autoEscape - }); - return resp.GetProperty("message_id").GetInt32(); - } - - public Task SendMessage(long? userId, long? groupId, MessageContent content, bool autoEscape = false) - { - return SendMessage(userId, groupId, content.ToCqMessage(), autoEscape); - } - - public async Task DeleteMessage(int messageId) - { - await _parent.SendApiRequest("delete_msg", - new { message_id = messageId }); - } - - public async Task GetMessage(int messageId) - { - return (await _parent.SendApiRequest("get_msg", - new { message_id = messageId })) - .Deserialize()!; - } - - public async Task MarkMessageAsRead(int messageId) - { - await _parent.SendApiRequest("mark_msg_as_read", - new { message_id = messageId }); - } - - public async Task SetGroupKick(long groupId, long userId, bool rejectAddRequest = false) - { - await _parent.SendApiRequest("set_group_kick", - new - { - group_id = groupId, - user_id = userId, - reject_add_request = rejectAddRequest - }); - } - - public async Task SetGroupBan(long groupId, long userId, int duration = 1800) - { - await _parent.SendApiRequest("set_group_ban", new - { - group_id = groupId, - user_id = userId, - duration = duration - }); - } - - public async Task SetGroupWholeBan(long groupId, bool enable = true) - { - await _parent.SendApiRequest("set_group_whole_ban", new - { - group_id = groupId, - enable = enable - }); - } - - public async Task SetGroupAdmin(long groupId, long userId, bool enable = true) - { - await _parent.SendApiRequest("set_group_admin", new - { - group_id = groupId, - user_id = userId, - enable = enable - }); - } - - public async Task SetGroupCard(long groupId, long userId, string card = "") - { - await _parent.SendApiRequest("set_group_card", new - { - group_id = groupId, - user_id = userId, - card = card - }); - } - - public async Task SetGroupName(long groupId, string groupName) - { - await _parent.SendApiRequest("set_group_name", new - { - group_id = groupId, - group_name = groupName - }); - } - - public async Task SetGroupLeave(long groupId, bool isDismiss = false) - { - await _parent.SendApiRequest("set_group_leave", new - { - group_id = groupId, - is_dismiss = isDismiss - }); - } - - public async Task SetGroupSpecialTitle(long groupId, long userId, string specialTitle = "", int duration = -1) - { - await _parent.SendApiRequest("set_group_special_title", new - { - group_id = groupId, - user_id = userId, - special_title = specialTitle, - duration = duration - }); - } - - public async Task SendGroupSign(long groupId) - { - await _parent.SendApiRequest("send_group_sign", new { group_id = groupId }); - } - - public async Task SetFriendAddRequest(string flag, bool approve = true, string remark = "") - { - await _parent.SendApiRequest("set_friend_add_request", new - { - flag, - approve, - remark - }); - } - - public async Task SetGroupAddRequest(string flag, string subType, bool approve = true, string reason = "") - { - await _parent.SendApiRequest("set_group_add_request", new - { - flag, - approve, - reason, - sub_type = subType - }); - } - - public async Task GetLoginInfo() - { - return (await _parent.SendApiRequest("get_login_info")).Deserialize()!; - } - - public async Task SetQqProfile(string nickname, string company, string email, string college, - string personalNote) - { - await _parent.SendApiRequest("set_qq_profile", new - { - nickname, - company, - email, - college, - personal_note = personalNote - }); - } - - public async Task GetStrangerInfo(long userId, bool noCache = false) - { - return (await _parent.SendApiRequest("get_stranger_info", new - { - user_id = userId, - no_cache = noCache - })) - .Deserialize()!; - } - - public async Task GetFriendList() - { - return (await _parent.SendApiRequest("get_friend_list")) - .Deserialize()!; - } - - public async Task DeleteFriend(long friendId) - { - await _parent.SendApiRequest("delete_friend", new { friend_id = friendId }); - } - - public async Task GetGroupInfo(long groupId, bool noCache = false) - { - return (await _parent.SendApiRequest("get_group_info", new - { - group_id = groupId, - no_cache = noCache - })) - .Deserialize()!; - } - - public async Task GetGroupList() - { - return (await _parent.SendApiRequest("get_group_list")) - .Deserialize()!; - } - - public async Task GetGroupMemberInfo(long groupId, long userId, bool noCache = false) - { - return (await _parent.SendApiRequest("get_group_member_info", new - { - group_id = groupId, - user_id = userId, - no_cache = noCache - })).Deserialize()!; - } - - public async Task GetGroupMemberList(long groupId, bool noCache = false) - { - return (await _parent.SendApiRequest("get_group_member_list", new - { - group_id = groupId, - no_cache = noCache - })) - .Deserialize()!; - } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotApiEvent.cs b/src/Flandre.Adapters.OneBot/Models/OneBotApiEvent.cs deleted file mode 100644 index 0697a95..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotApiEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Flandre.Adapters.OneBot.Models; - -internal class OneBotApiEvent -{ - [JsonPropertyName("time")] - public long Time { get; set; } - - [JsonPropertyName("self_id")] - public long SelfId { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotApiGuildMessageEvent.cs b/src/Flandre.Adapters.OneBot/Models/OneBotApiGuildMessageEvent.cs deleted file mode 100644 index 9e2b67b..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotApiGuildMessageEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Flandre.Adapters.OneBot.Models; - -// 频道专有 -internal class OneBotApiGuildMessageEvent : OneBotApiMessageEvent -{ - [JsonPropertyName("message_id")] - public new string? MessageId { get; set; } - - [JsonPropertyName("user_id")] - public new string? UserId { get; set; } - - [JsonPropertyName("message")] - public string? Message { get; set; } - - [JsonPropertyName("guild_id")] - public string? GuildId { get; set; } - - [JsonPropertyName("channel_id")] - public string? ChannelId { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotApiMessageEvent.cs b/src/Flandre.Adapters.OneBot/Models/OneBotApiMessageEvent.cs deleted file mode 100644 index 9a5f5d9..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotApiMessageEvent.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Text.Json.Serialization; - -#pragma warning disable CS8618 - -namespace Flandre.Adapters.OneBot.Models; - -internal class OneBotApiMessageEvent : OneBotApiEvent -{ - [JsonPropertyName("sub_type")] - public string SubType { get; set; } - - [JsonPropertyName("message_id")] - public int MessageId { get; set; } - - [JsonPropertyName("user_id")] - public long UserId { get; set; } - - // [JsonPropertyName("message")] - // public string Message { get; set; } - - [JsonPropertyName("raw_message")] - public string RawMessage { get; set; } - - // [JsonPropertyName("font")] - // public string Font { get; set; } - - [JsonPropertyName("sender")] - public OneBotMessageSender Sender { get; set; } - - [JsonPropertyName("message_type")] - public string MessageType { get; set; } - - // 私聊消息专有 - // [JsonPropertyName("temp_source")] - // public int? TempSource { get; set; } - - // 以下为群消息专有 - [JsonPropertyName("group_id")] - public long? GroupId { get; set; } - - [JsonPropertyName("anonymous")] - public object? Anonymous { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotApiRequestEvent.cs b/src/Flandre.Adapters.OneBot/Models/OneBotApiRequestEvent.cs deleted file mode 100644 index 1bb9033..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotApiRequestEvent.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json.Serialization; - -#pragma warning disable CS8618 - -namespace Flandre.Adapters.OneBot.Models; - -internal class OneBotApiRequestEvent : OneBotApiEvent -{ - [JsonPropertyName("request_type")] - public string RequestType { get; set; } - - [JsonPropertyName("flag")] - public string Flag { get; set; } - - [JsonPropertyName("comment")] - public string Comment { get; set; } - - [JsonPropertyName("user_id")] - public long UserId { get; set; } - - [JsonPropertyName("sub_type")] - public string? SubType { get; set; } - - [JsonPropertyName("group_id")] - public long? GroupId { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotApiResponse.cs b/src/Flandre.Adapters.OneBot/Models/OneBotApiResponse.cs deleted file mode 100644 index 695ff25..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotApiResponse.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -#pragma warning disable CS8618 - -namespace Flandre.Adapters.OneBot.Models; - -internal class OneBotApiResponse -{ - [JsonPropertyName("status")] - public string Status { get; set; } - - [JsonPropertyName("retcode")] - public int RetCode { get; set; } - - [JsonPropertyName("msg")] - public string? Msg { get; set; } - - [JsonPropertyName("wording")] - public string? Wording { get; set; } - - [JsonPropertyName("data")] - public JsonElement Data { get; set; } - - [JsonPropertyName("echo")] - public string Echo { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotFriend.cs b/src/Flandre.Adapters.OneBot/Models/OneBotFriend.cs deleted file mode 100644 index ad768c1..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotFriend.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json.Serialization; - -#pragma warning disable CS8618 - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotFriend -{ - [JsonPropertyName("user_id")] - public long UserId { get; set; } - - [JsonPropertyName("nickname")] - public string Nickname { get; set; } - - [JsonPropertyName("remark")] - public string Remark { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotGroup.cs b/src/Flandre.Adapters.OneBot/Models/OneBotGroup.cs deleted file mode 100644 index 683bd47..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotGroup.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; - -#pragma warning disable CS8618 - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotGroup -{ - [JsonPropertyName("group_id")] - public long GroupId { get; set; } - - [JsonPropertyName("group_name")] - public string GroupName { get; set; } - - [JsonPropertyName("group_memo")] - public string GroupMemo { get; set; } - - /// 若 Bot 未加入群,该项将会为 0。 - [JsonPropertyName("group_create_time")] - public uint GroupCreateTime { get; set; } - - /// 若 Bot 未加入群,该项将会为 0。 - [JsonPropertyName("group_level")] - public uint GroupLevel { get; set; } - - /// 若 Bot 未加入群,该项将会为 0。 - [JsonPropertyName("member_count")] - public int MemberCount { get; set; } - - /// 若 Bot 未加入群,该项将会为 0。 - [JsonPropertyName("max_member_count")] - public int MaxMemberCount { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotGroupMember.cs b/src/Flandre.Adapters.OneBot/Models/OneBotGroupMember.cs deleted file mode 100644 index 5331408..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotGroupMember.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Text.Json.Serialization; - -#pragma warning disable CS8618 - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotGroupMember -{ - [JsonPropertyName("group_id")] - public long GroupId { get; set; } - - [JsonPropertyName("user_id")] - public long UserId { get; set; } - - [JsonPropertyName("nickname")] - public string Nickname { get; set; } - - [JsonPropertyName("card")] - public string Card { get; set; } - - [JsonPropertyName("sex")] - public string Sex { get; set; } - - [JsonPropertyName("age")] - public int Age { get; set; } - - [JsonPropertyName("area")] - public string Area { get; set; } = ""; - - [JsonPropertyName("join_time")] - public int JoinTime { get; set; } - - [JsonPropertyName("last_sent_time")] - public int LastSentTime { get; set; } - - [JsonPropertyName("level")] - public string Level { get; set; } = ""; - - [JsonPropertyName("role")] - public string Role { get; set; } = ""; - - [JsonPropertyName("unfriendly")] - public bool Unfriendly { get; set; } - - [JsonPropertyName("title")] - public string Title { get; set; } = ""; - - [JsonPropertyName("title_expire_time")] - public long TitleExpireTime { get; set; } - - [JsonPropertyName("card_changeable")] - public bool CardChangeable { get; set; } - - [JsonPropertyName("shut_up_timestamp")] - public long ShutUpTimestamp { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotGuild.cs b/src/Flandre.Adapters.OneBot/Models/OneBotGuild.cs deleted file mode 100644 index b0960ea..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotGuild.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotGuild -{ - [JsonPropertyName("guild_id")] - public string? GuildId { get; set; } - - [JsonPropertyName("guild_name")] - public string? GuildName { get; set; } - - [JsonPropertyName("guild_display_id")] - public string? GuildDisplayId { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotGuildChannel.cs b/src/Flandre.Adapters.OneBot/Models/OneBotGuildChannel.cs deleted file mode 100644 index c36ec2b..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotGuildChannel.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotGuildChannel -{ - [JsonPropertyName("owner_guild_id")] - public string? OwnerGuildId { get; set; } - - [JsonPropertyName("channel_id")] - public string? ChannelId { get; set; } - - [JsonPropertyName("channel_type")] - public int ChannelType { get; set; } - - [JsonPropertyName("channel_name")] - public string? ChannelName { get; set; } - - [JsonPropertyName("create_time")] - public long CreateTime { get; set; } - - [JsonPropertyName("creator_tiny_id")] - public string? CreatorTinyId { get; set; } - - [JsonPropertyName("talk_permission")] - public int TalkPermission { get; set; } - - [JsonPropertyName("visible_type")] - public int VisibleType { get; set; } - - [JsonPropertyName("current_slow_mode")] - public int CurrentSlowMode { get; set; } - - [JsonPropertyName("slow_modes")] - public SlowModeInfo[]? SlowModes { get; set; } - - public class SlowModeInfo - { - [JsonPropertyName("slow_mode_key")] - public int SlowModeKey { get; set; } - - [JsonPropertyName("slow_mode_text")] - public string? SlowModeText { get; set; } - - [JsonPropertyName("speak_frequency")] - public int SpeekFrequency { get; set; } - - [JsonPropertyName("slow_mode_circle")] - public int SlowModeCircle { get; set; } - } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotGuildMember.cs b/src/Flandre.Adapters.OneBot/Models/OneBotGuildMember.cs deleted file mode 100644 index 9f8e27f..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotGuildMember.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotGuildMemberListResponse -{ - [JsonPropertyName("members")] - public OneBotGuildMember[]? Members { get; set; } - - [JsonPropertyName("finished")] - public bool Finished { get; set; } - - [JsonPropertyName("next_token")] - public string? NextToken { get; set; } -} - -public class OneBotGuildMember -{ - [JsonPropertyName("tiny_id")] - public string? TinyId { get; set; } - - [JsonPropertyName("title")] - public string? Title { get; set; } - - [JsonPropertyName("nickname")] - public string? Nickname { get; set; } - - [JsonPropertyName("role_id")] - public string? RoleId { get; set; } - - [JsonPropertyName("role_name")] - public string? RoleName { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotGuildMemberProfile.cs b/src/Flandre.Adapters.OneBot/Models/OneBotGuildMemberProfile.cs deleted file mode 100644 index 95fb722..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotGuildMemberProfile.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotGuildMemberProfile : OneBotGuildServiceProfile -{ - [JsonPropertyName("join_time")] - public long JoinTime { get; set; } - - [JsonPropertyName("roles")] - public RoleInfo[]? Roles { get; set; } - - public class RoleInfo - { - [JsonPropertyName("role_id")] - public string? RoleId { get; set; } - - [JsonPropertyName("role_name")] - public string? RoleName { get; set; } - } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotGuildMeta.cs b/src/Flandre.Adapters.OneBot/Models/OneBotGuildMeta.cs deleted file mode 100644 index b3e0413..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotGuildMeta.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotGuildMeta -{ - [JsonPropertyName("guild_id")] - public string? GuildId { get; set; } - - [JsonPropertyName("guild_name")] - public string? GuildName { get; set; } - - [JsonPropertyName("guild_profile")] - public string? GuildProfile { get; set; } - - [JsonPropertyName("create_time")] - public long CreateTime { get; set; } - - [JsonPropertyName("max_member_count")] - public long MaxMemberCount { get; set; } - - [JsonPropertyName("max_robot_count")] - public long MaxRobotCount { get; set; } - - [JsonPropertyName("max_admin_count")] - public long MaxAdminCount { get; set; } - - [JsonPropertyName("member_count")] - public long MemberCount { get; set; } - - [JsonPropertyName("owner_id")] - public string? OwnerId { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotGuildServiceProfile.cs b/src/Flandre.Adapters.OneBot/Models/OneBotGuildServiceProfile.cs deleted file mode 100644 index 37b3363..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotGuildServiceProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotGuildServiceProfile -{ - [JsonPropertyName("nickname")] - public string? Nickname { get; set; } - - [JsonPropertyName("tiny_id")] - public string? TinyId { get; set; } - - [JsonPropertyName("avatar_url")] - public string? AvatarUrl { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotLoginInfo.cs b/src/Flandre.Adapters.OneBot/Models/OneBotLoginInfo.cs deleted file mode 100644 index 0a338e9..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotLoginInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text.Json.Serialization; - -#pragma warning disable CS8618 - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotLoginInfo -{ - [JsonPropertyName("user_id")] - public long UserId { get; set; } - - [JsonPropertyName("nickname")] - public string Nickname { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotMessage.cs b/src/Flandre.Adapters.OneBot/Models/OneBotMessage.cs deleted file mode 100644 index ef65804..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotMessage.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json.Serialization; - -#pragma warning disable CS8618 - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotMessage -{ - [JsonPropertyName("message_id")] - public int MessageId { get; set; } - - [JsonPropertyName("real_id")] - public int RealId { get; set; } - - [JsonPropertyName("sender")] - public OneBotMessageSender Sender { get; set; } - - [JsonPropertyName("time")] - public int Time { get; set; } - - [JsonPropertyName("message")] - public object Message { get; set; } - - [JsonPropertyName("raw_message")] - public string RawMessage { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotMessageSender.cs b/src/Flandre.Adapters.OneBot/Models/OneBotMessageSender.cs deleted file mode 100644 index 41c8c32..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotMessageSender.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Text.Json.Serialization; - -#pragma warning disable CS8618 - -namespace Flandre.Adapters.OneBot.Models; - -public class OneBotMessageSender -{ - [JsonPropertyName("user_id")] - public long UserId { get; set; } - - [JsonPropertyName("nickname")] - public string Nickname { get; set; } - - [JsonPropertyName("sex")] - public string Sex { get; set; } - - [JsonPropertyName("age")] - public int Age { get; set; } - - // 以下为群聊专属 - - [JsonPropertyName("card")] - public string? Card { get; set; } - - [JsonPropertyName("area")] - public string? Area { get; set; } - - [JsonPropertyName("level")] - public string? Level { get; set; } - - [JsonPropertyName("role")] - public string? Role { get; set; } - - [JsonPropertyName("title")] - public string? Title { get; set; } - - // 以下为频道专属 - [JsonPropertyName("tiny_id")] - public string? TinyId { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Models/OneBotUser.cs b/src/Flandre.Adapters.OneBot/Models/OneBotUser.cs deleted file mode 100644 index b1d6bb2..0000000 --- a/src/Flandre.Adapters.OneBot/Models/OneBotUser.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Flandre.Adapters.OneBot.Models; - -#pragma warning disable CS8618 - -public class OneBotUser -{ - [JsonPropertyName("user_id")] - public long UserId { get; set; } - - [JsonPropertyName("nickname")] - public string Nickname { get; set; } - - [JsonPropertyName("sex")] - public string Sex { get; set; } - - [JsonPropertyName("age")] - public int Age { get; set; } - - [JsonPropertyName("qid")] - public string Qid { get; set; } - - [JsonPropertyName("level")] - public int Level { get; set; } - - [JsonPropertyName("login_days")] - public int LoginDays { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/OneBotAdapter.cs b/src/Flandre.Adapters.OneBot/OneBotAdapter.cs deleted file mode 100644 index c7d7b39..0000000 --- a/src/Flandre.Adapters.OneBot/OneBotAdapter.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Flandre.Core.Common; - -namespace Flandre.Adapters.OneBot; - -public class OneBotAdapter : IAdapter -{ - public IEnumerable Bots => _bots.AsReadOnly(); - - private readonly List _bots = new(); - - private readonly OneBotAdapterConfig _config; - - public OneBotAdapter(OneBotAdapterConfig config) - { - _config = config; - - foreach (var bot in _config.Bots) - switch (bot.Protocol) - { - case OneBotProtocol.WebSocket: - var obb = new OneBotWebSocketBot(bot); - _bots.Add(obb); - _bots.Add(obb.GuildBot); - break; - - default: - throw new NotSupportedException( - $"For now, the OneBot adapter only supports WebSocket protocol. Skipped initialization of bot {bot.SelfId}."); - } - } - - public async Task StartAsync() - { - await Task.WhenAll(_bots.Select(bot => bot.StartAsync())); - } - - public async Task StopAsync() - { - await Task.WhenAll(_bots.Select(bot => bot.StopAsync())); - } -} - -public class OneBotAdapterConfig -{ - /// - /// 构造 OneBot 适配器配置 - /// - public OneBotAdapterConfig() - { - Bots = new List(); - } - - /// - /// 构造 OneBot 适配器配置,并使用已有的 bot 配置列表 - /// - /// - public OneBotAdapterConfig(List bots) - { - Bots = bots; - } - - /// - /// bot 配置列表 - /// - public List Bots { get; init; } -} diff --git a/src/Flandre.Adapters.OneBot/OneBotBot.cs b/src/Flandre.Adapters.OneBot/OneBotBot.cs deleted file mode 100644 index c3bf9a2..0000000 --- a/src/Flandre.Adapters.OneBot/OneBotBot.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System.Text.Json; -using Flandre.Core.Common; -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Core.Models; - -namespace Flandre.Adapters.OneBot; - -public abstract class OneBotBot : Bot -{ - /// - /// Bot 平台名称,值为 onebot - /// - public override string Platform => "onebot"; - - /// - public override string SelfId { get; } - - internal readonly OneBotGuildBot GuildBot; - - /// - /// 内部 Bot,包含大量 OneBot 平台专属方法 - /// - public OneBotInternalBot Internal { get; } - - internal OneBotBot(string selfId) - { - SelfId = selfId; - Internal = new OneBotInternalBot(this); - GuildBot = new OneBotGuildBot(this); - } - - internal abstract Task SendApiRequest(string action, object? @params = null); - - #region 核心方法 - - /// - public override async Task SendChannelMessageAsync(string channelId, MessageContent content, - string? guildId = null) - { - return (await Internal.SendGroupMessage(long.Parse(channelId), content)).ToString(); - } - - /// - public override async Task SendPrivateMessageAsync(string userId, MessageContent content) - { - return (await Internal.SendPrivateMessage(long.Parse(userId), content)).ToString(); - } - - /// - public override Task DeleteMessageAsync(string messageId) - { - return Internal.DeleteMessage(int.Parse(messageId)); - } - - /// - public override async Task GetSelfAsync() - { - var user = await Internal.GetLoginInfo(); - return new User - { - Name = user.Nickname, - UserId = user.UserId.ToString(), - AvatarUrl = OneBotUtils.GetUserAvatar(user.UserId) - }; - } - - /// - public override async Task GetUserAsync(string userId, string? guildId = null) - { - var user = await Internal.GetStrangerInfo(long.Parse(userId)); - return new User - { - Name = user.Nickname, - UserId = user.UserId.ToString(), - AvatarUrl = OneBotUtils.GetUserAvatar(user.UserId) - }; - } - - /// - public override async Task> GetFriendListAsync() - { - var list = await Internal.GetFriendList(); - return list.Select(f => new User - { - Name = f.Nickname, - Nickname = f.Remark, - UserId = f.UserId.ToString(), - AvatarUrl = OneBotUtils.GetUserAvatar(f.UserId) - }); - } - - /// - public override async Task GetGuildAsync(string guildId) - { - try - { - var group = await Internal.GetGroupInfo(long.Parse(guildId)); - return new Guild - { - Id = group.GroupId.ToString(), - Name = group.GroupName - }; - } - catch - { - return null; - } - } - - /// - public override async Task> GetGuildListAsync() - { - var list = await Internal.GetGroupList(); - return list.Select(g => new Guild - { - Id = g.GroupId.ToString(), - Name = g.GroupName - }); - } - - /// - public override async Task GetGuildMemberAsync(string guildId, string userId) - { - try - { - var member = await Internal.GetGroupMemberInfo(long.Parse(guildId), long.Parse(userId)); - return new GuildMember - { - Name = member.Nickname, - Nickname = member.Card, - UserId = member.UserId.ToString(), - AvatarUrl = OneBotUtils.GetUserAvatar(member.UserId), - Roles = new List { member.Role } - }; - } - catch - { - return null; - } - } - - /// - public override async Task> GetGuildMemberListAsync(string guildId) - { - var list = await Internal.GetGroupMemberList(long.Parse(guildId)); - return list.Select(member => new GuildMember - { - Name = member.Nickname, - Nickname = member.Card, - UserId = member.UserId.ToString(), - AvatarUrl = OneBotUtils.GetUserAvatar(member.UserId), - Roles = new List { member.Role } - }); - } - - /// - public override async Task GetChannelAsync(string channelId, string? guildId = null) - { - try - { - var group = await Internal.GetGroupInfo(long.Parse(channelId)); - return new Channel - { - Id = group.GroupId.ToString(), - Name = group.GroupName - }; - } - catch - { - return null; - } - } - - /// - public override async Task> GetChannelListAsync(string guildId) - { - var list = await Internal.GetGroupList(); - return list.Select(c => new Channel - { - Id = c.GroupId.ToString(), - Name = c.GroupName - }); - } - - /// - public override async Task HandleGuildInvitationAsync(BotGuildInvitedEvent e, bool approve, string? comment = null) - { - await Internal.SetGroupAddRequest(e.EventPayload?.ToString()!, "invite", approve, comment ?? ""); - } - - /// - public override async Task HandleGuildJoinRequestAsync(BotGuildJoinRequestedEvent e, bool approve, - string? comment = null) - { - await Internal.SetGroupAddRequest(e.EventPayload?.ToString()!, "add", approve, comment ?? ""); - } - - /// - public override async Task HandleFriendRequestAsync(BotFriendRequestedEvent e, bool approve, string? comment = null) - { - await Internal.SetFriendAddRequest(e.EventPayload?.ToString()!, approve, comment ?? ""); - } - - #endregion -} diff --git a/src/Flandre.Adapters.OneBot/OneBotBotConfig.cs b/src/Flandre.Adapters.OneBot/OneBotBotConfig.cs deleted file mode 100644 index 4d13744..0000000 --- a/src/Flandre.Adapters.OneBot/OneBotBotConfig.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Flandre.Core.Common; - -namespace Flandre.Adapters.OneBot; - -/// -/// OneBot 通信协议 -/// -public enum OneBotProtocol -{ - /// - /// 正向 WebSocket - /// - WebSocket -} - -/// -/// OneBot 平台 Bot 配置 -/// -public class OneBotBotConfig : BotConfig -{ - /// - /// 连接 OneBot 服务端使用的协议。 - /// - public OneBotProtocol Protocol { get; set; } = OneBotProtocol.WebSocket; - - /// - /// 和 OneBot 服务端通信时使用的终结点。 - /// - public string? Endpoint { get; set; } = null; - - /// - /// WebSocket 服务端重连等待事件,单位为秒。 - /// - public int WebSocketReconnectTimeout { get; set; } = 10; -} diff --git a/src/Flandre.Adapters.OneBot/OneBotUtils.cs b/src/Flandre.Adapters.OneBot/OneBotUtils.cs deleted file mode 100644 index 33e7f98..0000000 --- a/src/Flandre.Adapters.OneBot/OneBotUtils.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Flandre.Adapters.OneBot; - -public static class OneBotUtils -{ - public static string EscapeCqCode(string source, bool escapeComma = false) - { - if (escapeComma) - source = source.Replace(",", ","); - return source - .Replace("&", "&") - .Replace("[", "[") - .Replace("]", "]"); - } - - public static string UnescapeCqCode(string source, bool unescapeComma = false) - { - if (unescapeComma) - source = source.Replace(",", ","); - return source - .Replace("&", "&") - .Replace("[", "[") - .Replace("]", "]"); - } - - public static string GetUserAvatar(string userId) - { - return $"http://q.qlogo.cn/headimg_dl?dst_uin={userId}&spec=640"; - } - - public static string GetUserAvatar(long userId) - { - return $"http://q.qlogo.cn/headimg_dl?dst_uin={userId}&spec=640"; - } -} - -public class OneBotApiException : Exception -{ - public OneBotApiException(string message) : base(message) - { - } -} diff --git a/src/Flandre.Adapters.OneBot/README.md b/src/Flandre.Adapters.OneBot/README.md deleted file mode 100644 index 6612601..0000000 --- a/src/Flandre.Adapters.OneBot/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Flandre.Adapters.OneBot - -基于 [OneBot](https://github.com/botuniverse/onebot) 协议实现的 QQ -协议适配器,主要对 [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 提供支持。同时基于 go-cqhttp 对 QQ 频道也进行了一定的支持。 - -[![NuGet](https://img.shields.io/nuget/vpre/Flandre.Adapters.OneBot?label=NuGet&color=blue)](https://www.nuget.org/packages/Flandre.Adapters.OneBot/) -[![NuGet Downloads](https://img.shields.io/nuget/dt/Flandre.Adapters.OneBot?label=Downloads&color=f06292)](https://www.nuget.org/packages/Flandre.Adapters.OneBot/) diff --git a/src/Flandre.Adapters.OneBot/Segments/OneBotImageSegment.cs b/src/Flandre.Adapters.OneBot/Segments/OneBotImageSegment.cs deleted file mode 100644 index 47a1e07..0000000 --- a/src/Flandre.Adapters.OneBot/Segments/OneBotImageSegment.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Flandre.Core.Messaging.Segments; - -namespace Flandre.Adapters.OneBot.Segments; - -public class OneBotImageSegment : ImageSegment -{ - public string? Filename { get; set; } - - public string? SubType { get; set; } - - public int? Id { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/Segments/OneBotRecordSegment.cs b/src/Flandre.Adapters.OneBot/Segments/OneBotRecordSegment.cs deleted file mode 100644 index 68d37ba..0000000 --- a/src/Flandre.Adapters.OneBot/Segments/OneBotRecordSegment.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Flandre.Core.Messaging.Segments; - -namespace Flandre.Adapters.OneBot.Segments; - -public class OneBotRecordSegment : AudioSegment -{ - public string? Filename { get; set; } - - public int? Magic { get; set; } -} diff --git a/src/Flandre.Adapters.OneBot/WebSocketBot.cs b/src/Flandre.Adapters.OneBot/WebSocketBot.cs deleted file mode 100644 index 3ee22b4..0000000 --- a/src/Flandre.Adapters.OneBot/WebSocketBot.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System.Collections.Concurrent; -using System.Text.Json; -using Flandre.Adapters.OneBot.Models; -using Flandre.Core.Common; -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Core.Models; -using Websocket.Client; -using Websocket.Client.Exceptions; - -namespace Flandre.Adapters.OneBot; - -public sealed class OneBotWebSocketBot : OneBotBot -{ - private readonly WebsocketClient _wsClient; - private bool _clientStopped; - - private readonly OneBotBotConfig _config; - - private readonly ConcurrentDictionary> _apiTasks = new(); - - public override event BotEventHandler? MessageReceived; - public override event BotEventHandler? GuildInvited; - public override event BotEventHandler? GuildJoinRequested; - public override event BotEventHandler? FriendRequested; - - internal OneBotWebSocketBot(OneBotBotConfig config) : base(config.SelfId) - { - _config = config; - - _wsClient = new WebsocketClient(new Uri(config.Endpoint!)) - { - ReconnectTimeout = TimeSpan.FromSeconds(_config.WebSocketReconnectTimeout), - ErrorReconnectTimeout = TimeSpan.FromSeconds(_config.WebSocketReconnectTimeout) - }; - - _wsClient.MessageReceived.Subscribe(OnApiMessage); - - _wsClient.DisconnectionHappened.Subscribe(_ => - { - if (_clientStopped) - return; - Log(BotLogLevel.Warning, - $"WebSocket connection lost, reconnecting in {_config.WebSocketReconnectTimeout}s..."); - foreach (var tcs in _apiTasks.Values) - tcs.SetException(new WebsocketException("WebSocket connection lost.")); - _apiTasks.Clear(); - }); - - _wsClient.ReconnectionHappened.Subscribe(_ => - { - Log(BotLogLevel.Information, - $"Successfully connected to WebSocket server {config.Endpoint}."); - }); - } - - #region WebSocket 交互 - - internal override Task SendApiRequest(string action, object? @params = null) - { - var tcs = new TaskCompletionSource(); - var echo = Guid.NewGuid().ToString(); - - _apiTasks.TryAdd(echo, tcs); - _wsClient.Send(JsonSerializer.Serialize(new - { - action, - @params, - echo - })); - - return tcs.Task; - } - - private void OnApiMessage(ResponseMessage msg) - { - var json = JsonDocument.Parse(msg.Text); - if (json.RootElement.TryGetProperty("post_type", out var postType)) - { - // 上报事件 - switch (postType.ToString()) - { - case "message": - json.RootElement.TryGetProperty("message_type", out var messageType); - if (messageType.ToString() == "guild") - GuildBot.InvokeMessageEvent(json.Deserialize()!); - else - OnApiMessageEvent(json.Deserialize()!); - break; - - case "request": - var requestEvent = json.Deserialize()!; - if (requestEvent.RequestType == "group") - { - if (requestEvent.SubType == "invite") - GuildInvited?.Invoke(this, new BotGuildInvitedEvent( - requestEvent.GroupId?.ToString()!, requestEvent.GroupId?.ToString()!, - requestEvent.UserId.ToString(), requestEvent.UserId.ToString(), true) - { - EventPayload = requestEvent.Flag - }); - else if (requestEvent.SubType == "add") - GuildJoinRequested?.Invoke(this, new BotGuildJoinRequestedEvent( - requestEvent.GroupId?.ToString()!, requestEvent.GroupId?.ToString()!, - requestEvent.UserId.ToString(), requestEvent.UserId.ToString(), - requestEvent.Comment) { EventPayload = requestEvent.Flag }); - } - else if (requestEvent.RequestType == "friend") - { - FriendRequested?.Invoke(this, new BotFriendRequestedEvent( - requestEvent.UserId.ToString(), requestEvent.UserId.ToString(), requestEvent.Comment) - { - EventPayload = requestEvent.Flag - }); - } - - break; - } - } - else - { - // 请求响应数据 - var resp = json.Deserialize()!; - if (!_apiTasks.TryGetValue(resp.Echo, out var tcs)) - return; - - if (resp.Status == "failed") - tcs.SetException(new OneBotApiException(resp.Msg ?? resp.Wording ?? $"调用 API 失败。({resp.RetCode})")); - else - tcs.SetResult(resp.Data); - - _apiTasks.TryRemove(resp.Echo, out _); - } - } - - private void OnApiMessageEvent(OneBotApiMessageEvent e) - { - MessageReceived?.Invoke(this, - new BotMessageReceivedEvent(new Message - { - Time = DateTimeOffset.FromUnixTimeSeconds(e.Time).DateTime, - Platform = Platform, - Environment = e.MessageType == "group" ? MessageEnvironment.Channel : MessageEnvironment.Private, - MessageId = e.MessageId.ToString(), - GuildId = e.GroupId?.ToString(), - ChannelId = e.GroupId?.ToString(), - Sender = new User - { - Name = e.Sender.Nickname, - Nickname = e.Sender.Card, - UserId = e.UserId.ToString(), - AvatarUrl = OneBotUtils.GetUserAvatar(e.UserId) - }, - Content = CqCodeParser.ParseCqMessage(e.RawMessage) - })); - } - - #endregion - - public override Task StartAsync() - { - _ = _wsClient.Start(); - _clientStopped = false; - return Task.CompletedTask; - } - - public override Task StopAsync() - { - _clientStopped = true; - _wsClient.Dispose(); - _apiTasks.Clear(); - return Task.CompletedTask; - } -} diff --git a/src/Flandre.Core.Reactive/CoreReactiveExtensions.cs b/src/Flandre.Core.Reactive/CoreReactiveExtensions.cs deleted file mode 100644 index 3245784..0000000 --- a/src/Flandre.Core.Reactive/CoreReactiveExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Reactive.Linq; -using Flandre.Core.Common; -using Flandre.Core.Events; - -namespace Flandre.Core.Reactive; - -public static class CoreReactiveExtensions -{ - private static IObservable OnBotEvent( - Action> add, Action> remove) where TEvent : FlandreEvent - { - return Observable.FromEventPattern, TEvent>(add, remove) - .Select(pattern => pattern.EventArgs); - } - - public static IObservable OnLogging(this Bot bot) - { - return OnBotEvent( - add => bot.Logging += add, - remove => bot.Logging -= remove); - } - - public static IObservable OnMessageReceived(this Bot bot) - { - return OnBotEvent( - add => bot.MessageReceived += add, - remove => bot.MessageReceived -= remove); - } - - public static IObservable OnGuildInvited(this Bot bot) - { - return OnBotEvent( - add => bot.GuildInvited += add, - remove => bot.GuildInvited -= remove); - } - - public static IObservable OnGuildJoinRequested(this Bot bot) - { - return OnBotEvent( - add => bot.GuildJoinRequested += add, - remove => bot.GuildJoinRequested -= remove); - } - - public static IObservable OnFriendRequested(this Bot bot) - { - return OnBotEvent( - add => bot.FriendRequested += add, - remove => bot.FriendRequested -= remove); - } -} diff --git a/src/Flandre.Core.Reactive/Flandre.Core.Reactive.csproj b/src/Flandre.Core.Reactive/Flandre.Core.Reactive.csproj deleted file mode 100644 index 0342cae..0000000 --- a/src/Flandre.Core.Reactive/Flandre.Core.Reactive.csproj +++ /dev/null @@ -1,37 +0,0 @@ - - - - Flandre.Core.Reactive - 1.0.0-rc.3 - FlandreDevs,bsdayo - Reactive Extensions (Rx.NET) support for Flandre.Core. - flandre;rx.net;reactive - MIT - avatar.jpg - - net6.0 - enable - enable - Library - true - CS1591 - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2022-2023 - - - - - - - - - - - - - - - diff --git a/src/Flandre.Core.Reactive/MessageExtensions.cs b/src/Flandre.Core.Reactive/MessageExtensions.cs deleted file mode 100644 index e4f4f63..0000000 --- a/src/Flandre.Core.Reactive/MessageExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Reactive.Linq; -using Flandre.Core.Messaging; - -namespace Flandre.Core.Reactive; - -public static class ObservableMessageExtensions -{ - public static IObservable OfPlatform( - this IObservable observable, - params string[] platforms) - { - return observable.Where(msg => - platforms.Any(p => msg.Platform.Equals(p, StringComparison.OrdinalIgnoreCase))); - } - - public static IObservable OfUser( - this IObservable observable, - params string[] userIds) - { - return observable.Where(msg => userIds.Contains(msg.Sender.UserId)); - } - - public static IObservable OfGuild( - this IObservable observable, - params string[] guildIds) - { - return observable.Where(msg => guildIds.Contains(msg.GuildId)); - } - - public static IObservable OfChannel( - this IObservable observable, - params string[] channelIds) - { - return observable.Where(msg => channelIds.Contains(msg.ChannelId)); - } - - public static IObservable InPrivate(this IObservable observable) - { - return observable.Where(msg => msg.Environment == MessageEnvironment.Private); - } - - public static IObservable InChannel(this IObservable observable) - { - return observable.Where(msg => msg.Environment == MessageEnvironment.Channel); - } -} diff --git a/src/Flandre.Core.Reactive/MessageReceivedExtensions.cs b/src/Flandre.Core.Reactive/MessageReceivedExtensions.cs deleted file mode 100644 index 3d74ef4..0000000 --- a/src/Flandre.Core.Reactive/MessageReceivedExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Reactive.Linq; -using Flandre.Core.Events; -using Flandre.Core.Messaging; - -namespace Flandre.Core.Reactive; - -public static class ObservableMessageReceivedExtensions -{ - public static IObservable OfPlatform( - this IObservable observable, - params string[] platforms) - { - return observable.Where(e => - platforms.Any(p => e.Message.Platform.Equals(p, StringComparison.OrdinalIgnoreCase))); - } - - public static IObservable OfUser( - this IObservable observable, - params string[] userIds) - { - return observable.Where(e => userIds.Contains(e.Message.Sender.UserId)); - } - - public static IObservable OfGuild( - this IObservable observable, - params string[] guildIds) - { - return observable.Where(e => guildIds.Contains(e.Message.GuildId)); - } - - public static IObservable OfChannel( - this IObservable observable, - params string[] channelIds) - { - return observable.Where(e => channelIds.Contains(e.Message.ChannelId)); - } - - public static IObservable InPrivate(this IObservable observable) - { - return observable.Where(e => e.Message.Environment == MessageEnvironment.Private); - } - - public static IObservable InChannel(this IObservable observable) - { - return observable.Where(e => e.Message.Environment == MessageEnvironment.Channel); - } -} diff --git a/src/Flandre.Core/AssemblyInfo.cs b/src/Flandre.Core/AssemblyInfo.cs deleted file mode 100644 index 5ad18d8..0000000 --- a/src/Flandre.Core/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Flandre.Framework")] -[assembly: InternalsVisibleTo("Flandre.Core.Tests")] diff --git a/src/Flandre.Core/Common/Bot.cs b/src/Flandre.Core/Common/Bot.cs deleted file mode 100644 index a37e4a2..0000000 --- a/src/Flandre.Core/Common/Bot.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Core.Models; - -namespace Flandre.Core.Common; - -/// -/// 机器人 -/// -public abstract partial class Bot -{ - /// - /// Bot 所在平台名称 - /// - public abstract string Platform { get; } - - /// - /// Bot 自身 ID - /// - public abstract string SelfId { get; } - - /// - /// 日志记录 - /// - /// 日志等级 - /// 日志消息 - public void Log(BotLogLevel level, string message) - { - Logging?.Invoke(this, new BotLoggingEvent(level, message)); - } - - /// - /// 启动 Bot 实例 - /// - public virtual Task StartAsync() => Task.CompletedTask; - - /// - /// 停止 Bot 实例 - /// - public virtual Task StopAsync() => Task.CompletedTask; - - /// - /// 该方法不受支持,发出警告 - /// - /// 方法名称 - /// Task.CompletedTask - protected Task LogNotSupportedAsync(string method) - { - Log(BotLogLevel.Debug, $"Platform {Platform} does not support method {method}."); - return Task.CompletedTask; - } - - /// - /// 该方法不受支持,发出警告 - /// - /// 方法名称 - /// 返回值 - /// Task.FromResult<TResult>(result) - protected Task LogNotSupportedAsync(string method, TResult result) - { - Log(BotLogLevel.Debug, $"Platform {Platform} does not support method {method}."); - return Task.FromResult(result); - } - - /// - /// 发送频道 (Channel) 消息 - /// - /// Channel ID - /// 消息内容 - /// 群组 ID - public virtual Task SendChannelMessageAsync(string channelId, MessageContent content, - string? guildId = null) - => LogNotSupportedAsync(nameof(SendChannelMessageAsync), null); - - /// - /// 发送私聊消息 - /// - /// 用户 ID - /// 消息内容 - public virtual Task SendPrivateMessageAsync(string userId, MessageContent content) - => LogNotSupportedAsync(nameof(SendPrivateMessageAsync), null); - - /// - /// 删除(撤回)消息 - /// - /// 消息 ID - public virtual Task DeleteMessageAsync(string messageId) - => LogNotSupportedAsync(nameof(DeleteMessageAsync)); - - /// - /// 获取自身信息 - /// - public virtual Task GetSelfAsync() - => LogNotSupportedAsync(nameof(GetSelfAsync), null); - - /// - /// 获取用户信息 - /// - /// 用户 ID - /// 群组 ID - public virtual Task GetUserAsync(string userId, string? guildId = null) - => LogNotSupportedAsync(nameof(GetUserAsync), null); - - /// - /// 获取好友列表 - /// - public virtual Task> GetFriendListAsync() - => LogNotSupportedAsync>(nameof(GetFriendListAsync), Array.Empty()); - - /// - /// 获取群组信息 - /// - /// 群组 ID - public virtual Task GetGuildAsync(string guildId) - => LogNotSupportedAsync(nameof(GetGuildAsync), null); - - /// - /// 获取群组列表 - /// - public virtual Task> GetGuildListAsync() - => LogNotSupportedAsync>(nameof(GetGuildListAsync), Array.Empty()); - - /// - /// 获取群组成员信息 - /// - /// 群组 ID - /// 用户 ID - public virtual Task GetGuildMemberAsync(string guildId, string userId) - => LogNotSupportedAsync(nameof(GetGuildMemberAsync), null); - - /// - /// 获取群组成员列表 - /// - /// 群组 ID - public virtual Task> GetGuildMemberListAsync(string guildId) - => LogNotSupportedAsync>(nameof(GetGuildListAsync), Array.Empty()); - - /// - /// 获取频道信息 - /// - /// 频道 ID - /// 群组 ID - public virtual Task GetChannelAsync(string channelId, string? guildId = null) - => LogNotSupportedAsync(nameof(GetChannelAsync), null); - - /// - /// 获取频道列表 - /// - /// 群组 ID - public virtual Task> GetChannelListAsync(string guildId) - => LogNotSupportedAsync>(nameof(GetGuildListAsync), Array.Empty()); -} - -/// -/// Bot 基本配置 -/// -public class BotConfig -{ - /// - /// 自身 ID - /// - public string SelfId { get; set; } = ""; -} diff --git a/src/Flandre.Core/Common/BotContext.cs b/src/Flandre.Core/Common/BotContext.cs deleted file mode 100644 index fe53c52..0000000 --- a/src/Flandre.Core/Common/BotContext.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Flandre.Core.Common; - -/// -/// 基础上下文 -/// -public class BotContext -{ - /// - /// 当前 bot 实例 - /// - public Bot Bot { get; } - - /// - /// 构造上下文 - /// - /// bot 实例 - public BotContext(Bot bot) - { - Bot = bot; - } - - /// - /// Bot 所在平台,等同于 Bot.Platform。 - /// - public string Platform => Bot.Platform; - - /// - /// Bot 自身 ID,等同于 Bot.SelfId。 - /// - public string SelfId => Bot.SelfId; -} diff --git a/src/Flandre.Core/Common/BotEvents.cs b/src/Flandre.Core/Common/BotEvents.cs deleted file mode 100644 index a95e1d9..0000000 --- a/src/Flandre.Core/Common/BotEvents.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Flandre.Core.Events; - -namespace Flandre.Core.Common; - -/// -/// Bot 事件委托 -/// -/// 事件类型 -public delegate void BotEventHandler(Bot bot, TEvent e) where TEvent : FlandreEvent; - -public abstract partial class Bot -{ - /// - /// 日志记录 - /// - public event BotEventHandler? Logging; - - /// - /// 收到消息 - /// - public abstract event BotEventHandler? MessageReceived; - - /// - /// 收到群组邀请 - /// - public abstract event BotEventHandler? GuildInvited; - - /// - /// 收到加群申请 - /// - public abstract event BotEventHandler? GuildJoinRequested; - - /// - /// 收到好友申请 - /// - public abstract event BotEventHandler? FriendRequested; - - /// - /// 处理拉群邀请 - /// - /// 拉群邀请事件 - /// 是否同意 - /// 附加说明 - public virtual Task HandleGuildInvitationAsync(BotGuildInvitedEvent e, bool approve, string? comment = null) - => LogNotSupportedAsync(nameof(HandleGuildInvitationAsync)); - - /// - /// 处理加群申请 - /// - /// 加群申请事件 - /// 是否同意 - /// 附加说明 - public virtual Task HandleGuildJoinRequestAsync(BotGuildJoinRequestedEvent e, bool approve, string? comment = null) - => LogNotSupportedAsync(nameof(HandleGuildJoinRequestAsync)); - - /// - /// 处理好友申请 - /// - /// 好友申请事件 - /// 是否同意 - /// 附加说明 - public virtual Task HandleFriendRequestAsync(BotFriendRequestedEvent e, bool approve, string? comment = null) - => LogNotSupportedAsync(nameof(HandleFriendRequestAsync)); -} diff --git a/src/Flandre.Core/Common/BotExtensions.cs b/src/Flandre.Core/Common/BotExtensions.cs deleted file mode 100644 index 9d5fc5e..0000000 --- a/src/Flandre.Core/Common/BotExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Flandre.Core.Messaging; - -namespace Flandre.Core.Common; - -/// -/// 机器人扩展方法 -/// -public static class BotExtensions -{ - /// - /// 发送消息 - /// - /// 发送消息的机器人 - /// 消息类型 - /// 频道 ID - /// 用户 ID - /// 消息内容 - /// 群组 ID - public static Task SendMessageAsync(this Bot bot, MessageEnvironment environment, string? channelId, - string? userId, - MessageContent content, - string? guildId = null) - { - return environment switch - { - MessageEnvironment.Channel => bot.SendChannelMessageAsync(channelId!, content, guildId), - MessageEnvironment.Private => bot.SendPrivateMessageAsync(userId!, content), - _ => Task.FromResult(null) - }; - } - - /// - /// 发送消息 - /// - /// 发送消息的机器人 - /// 消息对象 - /// 覆盖消息对象的内容,可选 - public static Task SendMessageAsync(this Bot bot, Message message, MessageContent? contentOverride = null) - { - return SendMessageAsync(bot, message.Environment, message.ChannelId, message.Sender.UserId, - contentOverride ?? message.Content, message.GuildId); - } -} diff --git a/src/Flandre.Core/Common/BotLogLevel.cs b/src/Flandre.Core/Common/BotLogLevel.cs deleted file mode 100644 index ac1c077..0000000 --- a/src/Flandre.Core/Common/BotLogLevel.cs +++ /dev/null @@ -1,17 +0,0 @@ -#pragma warning disable CS1591 - -namespace Flandre.Core.Common; - -/// -/// 日志等级,与 Microsoft.Extension.Logging.LogLevel 兼容。 -/// -public enum BotLogLevel -{ - Trace = 0, - Debug = 1, - Information = 2, - Warning = 3, - Error = 4, - Critical = 5, - None = 6 -} diff --git a/src/Flandre.Core/Common/IAdapter.cs b/src/Flandre.Core/Common/IAdapter.cs deleted file mode 100644 index 8b95d0d..0000000 --- a/src/Flandre.Core/Common/IAdapter.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Flandre.Core.Common; - -/// -/// 适配器接口 -/// -public interface IAdapter -{ - /// - /// 适配器机器人列表 - /// - public IEnumerable Bots { get; } - - /// - /// 启动适配器 - /// - public Task StartAsync(); - - /// - /// 停止适配器 - /// - public Task StopAsync(); -} diff --git a/src/Flandre.Core/Events/BotFriendRequestedEvent.cs b/src/Flandre.Core/Events/BotFriendRequestedEvent.cs deleted file mode 100644 index 2beac15..0000000 --- a/src/Flandre.Core/Events/BotFriendRequestedEvent.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace Flandre.Core.Events; - -/// -/// bot 好友申请事件 -/// -public class BotFriendRequestedEvent : FlandreEvent -{ - /// - /// 申请人名称 - /// - public string RequesterName { get; } - - /// - /// 申请人 ID - /// - public string RequesterId { get; } - - /// - /// - /// - public string Comment { get; } - - /// - /// 构造好友申请事件 - /// - /// 申请人名称 - /// 申请人 ID - /// 申请备注 - public BotFriendRequestedEvent(string requesterName, string requesterId, string comment) - { - RequesterName = requesterName; - RequesterId = requesterId; - Comment = comment; - } -} diff --git a/src/Flandre.Core/Events/BotGuildInvitedEvent.cs b/src/Flandre.Core/Events/BotGuildInvitedEvent.cs deleted file mode 100644 index bef997b..0000000 --- a/src/Flandre.Core/Events/BotGuildInvitedEvent.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Flandre.Core.Events; - -/// -/// bot 收到 Guild 邀请事件 -/// -public class BotGuildInvitedEvent : FlandreEvent -{ - /// - /// Guild 名称 - /// - public string GuildName { get; } - - /// - /// Guild ID - /// - public string GuildId { get; } - - /// - /// 邀请人 ID - /// - public string InviterId { get; } - - /// - /// 邀请人名称 - /// - public string InviterName { get; } - - /// - /// 邀请人是否为管理员。如果适配器不支持应始终返回 true。 - /// - public bool InviterIsAdmin { get; } - - /// - /// 构造事件 - /// - /// Guild 名称 - /// Guild ID - /// 邀请人名称 - /// 邀请人 ID - /// 邀请人是否为管理员 - public BotGuildInvitedEvent(string guildName, string guildId, - string inviterName, string inviterId, bool inviterIsAdmin) - { - GuildName = guildName; - GuildId = guildId; - InviterName = inviterName; - InviterId = inviterId; - InviterIsAdmin = inviterIsAdmin; - } -} diff --git a/src/Flandre.Core/Events/BotGuildJoinRequestedEvent.cs b/src/Flandre.Core/Events/BotGuildJoinRequestedEvent.cs deleted file mode 100644 index de9dde0..0000000 --- a/src/Flandre.Core/Events/BotGuildJoinRequestedEvent.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Flandre.Core.Events; - -/// -/// 入群申请事件 -/// -public class BotGuildJoinRequestedEvent : FlandreEvent -{ - /// - /// Guild 名称 - /// - public string GuildName { get; } - - /// - /// Guild ID - /// - public string GuildId { get; } - - /// - /// 申请人名称 - /// - public string RequesterName { get; } - - /// - /// 申请人 ID - /// - public string RequesterId { get; } - - /// - /// 申请备注 - /// - public string Comment { get; } - - /// - /// 构造事件 - /// - /// Guild 名称 - /// Guild ID - /// 申请人名称 - /// 申请人 ID - /// 申请备注 - public BotGuildJoinRequestedEvent(string guildName, string guildId, string requesterName, string requesterId, - string comment) - { - GuildName = guildName; - GuildId = guildId; - RequesterName = requesterName; - RequesterId = requesterId; - Comment = comment; - } -} diff --git a/src/Flandre.Core/Events/BotLoggingEvent.cs b/src/Flandre.Core/Events/BotLoggingEvent.cs deleted file mode 100644 index 1ea36aa..0000000 --- a/src/Flandre.Core/Events/BotLoggingEvent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Flandre.Core.Common; - -namespace Flandre.Core.Events; - -/// -/// Bot 日志记录事件 -/// -public class BotLoggingEvent : FlandreEvent -{ - /// - /// 日志等级 - /// - public BotLogLevel LogLevel { get; } - - /// - /// 日志消息 - /// - public string LogMessage { get; } - - internal BotLoggingEvent(BotLogLevel level, string message) - { - LogLevel = level; - LogMessage = message; - } -} diff --git a/src/Flandre.Core/Events/BotMessageReceivedEvent.cs b/src/Flandre.Core/Events/BotMessageReceivedEvent.cs deleted file mode 100644 index 677b636..0000000 --- a/src/Flandre.Core/Events/BotMessageReceivedEvent.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Flandre.Core.Messaging; - -namespace Flandre.Core.Events; - -/// -/// 消息接收事件 -/// -public class BotMessageReceivedEvent : FlandreEvent -{ - /// - /// 接收到的消息 - /// - public Message Message { get; } - - /// - /// 构造事件 - /// - /// 接收到的消息 - public BotMessageReceivedEvent(Message message) - { - Message = message; - } -} diff --git a/src/Flandre.Core/Events/FlandreEvent.cs b/src/Flandre.Core/Events/FlandreEvent.cs deleted file mode 100644 index 0a587a0..0000000 --- a/src/Flandre.Core/Events/FlandreEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Flandre.Core.Events; - -/// -/// 基础事件 -/// -public abstract class FlandreEvent : EventArgs -{ - /// - /// 事件时间 - /// - public DateTime EventTime { get; init; } - - /// - /// 事件载荷 - /// - public object? EventPayload { get; init; } - - internal FlandreEvent() - { - EventTime = DateTime.Now; - } -} diff --git a/src/Flandre.Core/Flandre.Core.csproj b/src/Flandre.Core/Flandre.Core.csproj deleted file mode 100644 index db1f7b9..0000000 --- a/src/Flandre.Core/Flandre.Core.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - Flandre.Core - 1.0.0-rc.4 - FlandreDevs,bsdayo - 跨平台聊天机器人框架核心,提供基本交互操作,容易嵌入到已有项目中。 - bot;chatbot;flandre - MIT - avatar.jpg - README.NuGet.md - - net6.0 - enable - enable - Library - true - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2022-2023 - - - - - - - - diff --git a/src/Flandre.Core/Messaging/Message.cs b/src/Flandre.Core/Messaging/Message.cs deleted file mode 100644 index 27b0620..0000000 --- a/src/Flandre.Core/Messaging/Message.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Flandre.Core.Models; - -namespace Flandre.Core.Messaging; - -#pragma warning disable CS8618 - -/// -/// 消息结构 -/// -public class Message -{ - /// - /// 消息时间 - /// - public DateTime Time { get; init; } = DateTime.Now; - - /// - /// 平台 ID - /// - public string Platform { get; init; } = string.Empty; - - /// - /// 消息来源类型 - /// - public MessageEnvironment Environment { get; init; } - - /// - /// 消息 ID - /// - public string MessageId { get; init; } = string.Empty; - - /// - /// Guild ID - /// - public string? GuildId { get; init; } - - /// - /// Channel ID - /// - public string? ChannelId { get; init; } - - /// - /// 发送者信息 - /// - public User Sender { get; init; } - - /// - /// 消息内容 - /// - public MessageContent Content { get; init; } - - /// - /// 获取消息内容中的所有文本 - /// - public string GetText() - { - return Content.GetText(); - } -} - -/// -/// 消息来源类型 -/// -public enum MessageEnvironment -{ - /// - /// 来自 Channel - /// - Channel, - - /// - /// 来自私聊 - /// - Private -} diff --git a/src/Flandre.Core/Messaging/MessageBuilder.cs b/src/Flandre.Core/Messaging/MessageBuilder.cs deleted file mode 100644 index 0774ef2..0000000 --- a/src/Flandre.Core/Messaging/MessageBuilder.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Flandre.Core.Messaging.Segments; - -namespace Flandre.Core.Messaging; - -/// -/// 消息构造器 -/// -public class MessageBuilder -{ - /// - /// 已包含消息段 - /// - public List Segments = new(); - - /// - /// 添加文本消息段 - /// - /// 文本 - public MessageBuilder Text(string text) - { - Segments.Add(new TextSegment(text)); - return this; - } - - /// - /// 添加图片消息段 - /// - /// 图片消息段 - public MessageBuilder Image(ImageSegment imageSegment) - { - Segments.Add(imageSegment); - return this; - } - - /// - /// 添加图片消息段 - /// - /// 图片数据 - public MessageBuilder Image(byte[] data) - { - return Image(ImageSegment.FromData(data)); - } - - /// - /// 添加消息段 - /// - /// 消息段 - public MessageBuilder Add(MessageSegment segment) - { - Segments.Add(segment); - return this; - } - - /// - /// 构造为 MessageContent - /// - public MessageContent Build() - { - return new MessageContent(Segments); - } -} diff --git a/src/Flandre.Core/Messaging/MessageContent.cs b/src/Flandre.Core/Messaging/MessageContent.cs deleted file mode 100644 index d1c90fa..0000000 --- a/src/Flandre.Core/Messaging/MessageContent.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Collections; -using Flandre.Core.Messaging.Segments; - -namespace Flandre.Core.Messaging; - -/// -/// 消息内容 -/// -public class MessageContent : IEnumerable -{ - /// - /// 消息内容包含的消息段 - /// - public IEnumerable Segments { get; } - - /// - /// 使用消息段构造消息内容 - /// - /// - public MessageContent(IEnumerable segments) - { - Segments = segments; - } - - /// - /// 获取类型匹配的消息段 - /// - /// 消息段类型 - public TSegment? GetSegment() where TSegment : MessageSegment - { - return (TSegment?)Segments.FirstOrDefault(segment => segment is TSegment); - } - - /// - /// 获取所有类型匹配的消息段 - /// - /// 消息段类型 - public IEnumerable GetSegments() where TSegment : MessageSegment - { - return Segments.Where(segment => segment is TSegment).Cast(); - } - - /// - /// 获取消息内容中的所有文本 - /// - public string GetText() - { - return string.Join("", GetSegments().Select(s => s.Text)); - } - - /// - /// 由 Message 隐式转换 - /// - public static implicit operator MessageContent(Message message) - { - return message.Content; - } - - /// - /// 由 MessageBuilder 隐式转换 - /// - public static implicit operator MessageContent(MessageBuilder builder) - { - return builder.Build(); - } - - /// - /// 由消息段隐式转换 - /// - public static implicit operator MessageContent(MessageSegment segment) - { - return new MessageContent(new[] { segment }); - } - - /// - /// 由字符串隐式转换 - /// - public static implicit operator MessageContent(string? text) - { - return text is null - ? new MessageContent(Array.Empty()) - : new MessageContent(new[] { new TextSegment(text) }); - } - - /// - /// 获取 Enumerator - /// - /// - public IEnumerator GetEnumerator() - { - return Segments.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } -} diff --git a/src/Flandre.Core/Messaging/MessageContext.cs b/src/Flandre.Core/Messaging/MessageContext.cs deleted file mode 100644 index db7fe46..0000000 --- a/src/Flandre.Core/Messaging/MessageContext.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Flandre.Core.Common; - -namespace Flandre.Core.Messaging; - -/// -/// 消息上下文 -/// -public class MessageContext : BotContext -{ - /// - /// 当前消息 - /// - public Message Message { get; init; } - - /// - /// 构造消息上下文 - /// - /// bot 实例 - /// 消息 - public MessageContext(Bot bot, Message message) - : base(bot) - { - Message = message; - } - - /// - /// 用户 ID,等同于 .Sender.UserId - /// - public string UserId => Message.Sender.UserId; - - /// - /// 群组 ID,等同于 .GuildId - /// - public string? GuildId => Message.GuildId; - - /// - /// 频道 ID,等同于 .ChannelId - /// - public string? ChannelId => Message.ChannelId; -} diff --git a/src/Flandre.Core/Messaging/MessageSegment.cs b/src/Flandre.Core/Messaging/MessageSegment.cs deleted file mode 100644 index f7590e7..0000000 --- a/src/Flandre.Core/Messaging/MessageSegment.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Flandre.Core.Messaging; - -/// -/// 消息段基类 -/// -public abstract class MessageSegment -{ -} - -/// -/// 内联消息段 -/// -public abstract class InlineSegment : MessageSegment -{ -} - -/// -/// 资源消息段 -/// -public abstract class ResourceSegment : MessageSegment -{ - /// - /// 资源数据 - /// - public byte[]? Data { get; set; } - - /// - /// 资源文件路径 - /// - public string? Path { get; set; } - - /// - /// 资源 URL - /// - public string? Url { get; set; } -} - -/// -/// 前缀消息段 -/// -public abstract class PrefixSegment : MessageSegment -{ -} diff --git a/src/Flandre.Core/Messaging/Segments/AtSegment.cs b/src/Flandre.Core/Messaging/Segments/AtSegment.cs deleted file mode 100644 index 698eb6c..0000000 --- a/src/Flandre.Core/Messaging/Segments/AtSegment.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace Flandre.Core.Messaging.Segments; - -/// -/// at 消息段 -/// -public class AtSegment : InlineSegment -{ - /// - /// 用户 ID - /// - public string? UserId { get; set; } - - /// - /// at 范围 - /// - public AtSegmentScope Scope { get; set; } = AtSegmentScope.Single; - - /// - /// 使用用户 ID 构造 at 消息段 - /// - /// 用户 ID - public AtSegment(string userId) - { - UserId = userId; - } - - /// - /// 使用用户 ID 构造 at 消息段 - /// - /// at 范围 - public AtSegment(AtSegmentScope scope) - { - Scope = scope; - } - - /// - /// 快捷 at 全体成员 - /// - public static AtSegment AtAll() - { - return new AtSegment(AtSegmentScope.All); - } -} - -/// -/// at 范围 -/// -public enum AtSegmentScope -{ - /// - /// at 单个用户 - /// - Single, - - /// - /// at 全体成员 - /// - All -} diff --git a/src/Flandre.Core/Messaging/Segments/AudioSegment.cs b/src/Flandre.Core/Messaging/Segments/AudioSegment.cs deleted file mode 100644 index 445973a..0000000 --- a/src/Flandre.Core/Messaging/Segments/AudioSegment.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Flandre.Core.Messaging.Segments; - -/// -/// 语音消息段 -/// -public class AudioSegment : ResourceSegment -{ - /// - /// 从数据构造 - /// - /// 图片数据 - public static AudioSegment FromData(byte[] data) - { - return new AudioSegment { Data = data }; - } - - /// - /// 从本地路径构建 - /// - /// 本地路径 - public static AudioSegment FromPath(string path) - { - return new AudioSegment { Path = path }; - } - - /// - /// 从网络 URL 构建 - /// - /// 网络 URL - public static AudioSegment FromUrl(string url) - { - return new AudioSegment { Url = url }; - } -} diff --git a/src/Flandre.Core/Messaging/Segments/FaceSegment.cs b/src/Flandre.Core/Messaging/Segments/FaceSegment.cs deleted file mode 100644 index 3932447..0000000 --- a/src/Flandre.Core/Messaging/Segments/FaceSegment.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Flandre.Core.Messaging.Segments; - -/// -/// 表情消息段 -/// -public class FaceSegment : InlineSegment -{ - /// - /// 表情 ID - /// - public string FaceId { get; set; } - - /// - /// 构造表情消息段 - /// - /// - public FaceSegment(string faceId) - { - FaceId = faceId; - } -} diff --git a/src/Flandre.Core/Messaging/Segments/ImageSegment.cs b/src/Flandre.Core/Messaging/Segments/ImageSegment.cs deleted file mode 100644 index 2cd8c9b..0000000 --- a/src/Flandre.Core/Messaging/Segments/ImageSegment.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace Flandre.Core.Messaging.Segments; - -/// -/// 图片消息段 -/// -public class ImageSegment : ResourceSegment -{ - /// - /// 图片类型,提供给适配器做相应实现 - /// - public string? Type { get; set; } - - /// - /// 从数据构造 - /// - /// 图片数据 - /// 类型 - public static ImageSegment FromData(byte[] data, string? type = null) - { - return new ImageSegment - { - Data = data, - Type = type - }; - } - - /// - /// 从本地路径构建 - /// - /// 本地路径 - /// 类型 - public static ImageSegment FromPath(string path, string? type = null) - { - return new ImageSegment - { - Path = path, - Type = type - }; - } - - /// - /// 从网络 URL 构建 - /// - /// 网络 URL - /// 类型 - public static ImageSegment FromUrl(string url, string? type = null) - { - return new ImageSegment - { - Url = url, - Type = type - }; - } -} diff --git a/src/Flandre.Core/Messaging/Segments/QuoteSegment.cs b/src/Flandre.Core/Messaging/Segments/QuoteSegment.cs deleted file mode 100644 index b72e3a2..0000000 --- a/src/Flandre.Core/Messaging/Segments/QuoteSegment.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Flandre.Core.Messaging.Segments; - -/// -/// 引用消息段(如回复) -/// -public class QuoteSegment : PrefixSegment -{ - /// - /// 引用的消息 - /// - public Message QuotedMessage { get; set; } - - /// - /// 构造引用消息段 - /// - /// 引用的消息 - public QuoteSegment(Message message) - { - QuotedMessage = message; - } -} diff --git a/src/Flandre.Core/Messaging/Segments/TextSegment.cs b/src/Flandre.Core/Messaging/Segments/TextSegment.cs deleted file mode 100644 index 95d19a4..0000000 --- a/src/Flandre.Core/Messaging/Segments/TextSegment.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Flandre.Core.Messaging.Segments; - -/// -/// 文本消息段 -/// -public class TextSegment : InlineSegment -{ - /// - /// 文本 - /// - public string Text { get; set; } - - /// - /// 构造文本消息段 - /// - /// 文本 - public TextSegment(string text) - { - Text = text; - } -} diff --git a/src/Flandre.Core/Models/Channel.cs b/src/Flandre.Core/Models/Channel.cs deleted file mode 100644 index 3e3b7fe..0000000 --- a/src/Flandre.Core/Models/Channel.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Flandre.Core.Models; - -/// -/// Channel 信息 -/// -public class Channel -{ - /// - /// Channel ID - /// - public string Id { get; init; } = ""; - - /// - /// Channel 名称 - /// - public string Name { get; init; } = ""; -} diff --git a/src/Flandre.Core/Models/Guild.cs b/src/Flandre.Core/Models/Guild.cs deleted file mode 100644 index 397bbcd..0000000 --- a/src/Flandre.Core/Models/Guild.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Flandre.Core.Models; - -/// -/// Guild 信息 -/// -public class Guild -{ - /// - /// Guild ID - /// - public string Id { get; init; } = ""; - - /// - /// Guild 名称 - /// - public string Name { get; init; } = ""; -} diff --git a/src/Flandre.Core/Models/GuildMember.cs b/src/Flandre.Core/Models/GuildMember.cs deleted file mode 100644 index 5a1289e..0000000 --- a/src/Flandre.Core/Models/GuildMember.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Flandre.Core.Models; - -/// -/// Guild 成员 -/// -public class GuildMember : User -{ - /// - /// 成员角色 - /// - public List Roles { get; init; } = new(); -} diff --git a/src/Flandre.Core/Models/User.cs b/src/Flandre.Core/Models/User.cs deleted file mode 100644 index b163125..0000000 --- a/src/Flandre.Core/Models/User.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Flandre.Core.Models; - -/// -/// 用户信息 -/// -public class User -{ - /// - /// 用户名称 - /// - public string Name { get; init; } = ""; - - /// - /// 用户昵称 - /// - public string? Nickname { get; init; } - - /// - /// 用户 ID - /// - public string UserId { get; init; } = ""; - - /// - /// 用户头像 URL - /// - public string? AvatarUrl { get; init; } -} diff --git a/src/Flandre.Core/Models/UserRole.cs b/src/Flandre.Core/Models/UserRole.cs deleted file mode 100644 index 7513fd5..0000000 --- a/src/Flandre.Core/Models/UserRole.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Flandre.Core.Models; - -/// -/// 用户身份 -/// -public enum UserRole -{ - /// - /// 常规成员 - /// - Member, - - /// - /// 管理员 - /// - Admin, - - /// - /// 所有者 - /// - Owner -} diff --git a/src/Flandre.Core/Utils/FlandreCoreUtils.cs b/src/Flandre.Core/Utils/FlandreCoreUtils.cs deleted file mode 100644 index 73c9a21..0000000 --- a/src/Flandre.Core/Utils/FlandreCoreUtils.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Flandre.Core.Messaging; - -namespace Flandre.Core.Utils; - -/// -/// Flandre 工具方法 -/// -public static class FlandreCoreUtils -{ - /// - /// 自动检测 的属性并异步获取或下载资源。 - /// 优先级顺序为:直接返回 Data -> 根据 Path 读取文件 -> 根据 Url 下载文件 - /// - /// 资源消息段 - public static async Task GetOrDownloadDataAsync(this ResourceSegment resource) - { - if (resource.Data is not null) - return resource.Data; - - if (resource.Path is not null && File.Exists(resource.Path)) - return await File.ReadAllBytesAsync(resource.Path); - - if (resource.Url is not null) - return await new HttpClient().GetByteArrayAsync(resource.Url); - - return null; - } -} diff --git a/src/Flandre.Core/Utils/StringParser.cs b/src/Flandre.Core/Utils/StringParser.cs deleted file mode 100644 index dcde308..0000000 --- a/src/Flandre.Core/Utils/StringParser.cs +++ /dev/null @@ -1,222 +0,0 @@ -namespace Flandre.Core.Utils; - -/// -/// 字符串解析器 -/// -public class StringParser -{ - private readonly string _str; - private int _pos; - - /// - /// 左侧引号 - /// - public char[] LeftQuotes { get; } - - /// - /// 右侧引号 - /// - public char[] RightQuotes { get; } - - /// - /// 当前字符 - /// - public char Current => _str[_pos]; - - /// - /// 字符串是否解析完 - /// - public bool IsEnd => _pos >= _str.Length; - - // ReSharper disable SuggestBaseTypeForParameterInConstructor - /// - /// - /// 本方法会在 添加 '(直单引号)、"(直双引号)、`(反引号) 三个字符。如果这不是预期的行为,请传入副本 - /// - public StringParser(string str, HashSet quoteChars, HashSet<(char Left, char Right)> quotePairs) - { - _str = str; - _ = quoteChars.Add('\''); - _ = quoteChars.Add('"'); - _ = quoteChars.Add('`'); - LeftQuotes = quotePairs.Select(t => t.Left).Concat(quoteChars).Where(c => !char.IsWhiteSpace(c)).ToArray(); - RightQuotes = quotePairs.Select(t => t.Right).Concat(quoteChars).Where(c => !char.IsWhiteSpace(c)).ToArray(); - } - // ReSharper restore SuggestBaseTypeForParameterInConstructor - - /// - /// 构造字符串构造解析器实例 - /// - public StringParser(string str, params char[] quoteChars) - : this(str, quoteChars.ToHashSet(), new HashSet<(char Left, char Right)>()) - { - } - - #region Skip - - /// - /// 跳过指定长度 - /// - public StringParser Skip(int length) - { - _pos += length; - return this; - } - - /// - /// 跳到指定的字符位置 - /// - /// 终点字符 - /// - public StringParser Skip(char terminator, bool includeTerminator = false) - { - _pos = IndexOfOrEnd(terminator); - if (includeTerminator) - ++_pos; - return this; - } - - /// - /// 跳到指定的字符位置 - /// - /// false时停止 - /// - public StringParser SkipWhen(Func predicate, bool includeTerminator = false) - { - while (!(IsEnd || !predicate(Current))) - ++_pos; - if (includeTerminator) - ++_pos; - return this; - } - - /// - /// 跳过空白字符 - /// - public StringParser SkipWhiteSpaces() => SkipWhen(char.IsWhiteSpace); - - #endregion - - #region Peek - - /// - /// 读取字符串,但不移动解析器指针 - /// - /// 读取字符串的长度 - public string Peek(int length) => _str.Substring(_pos, length); - - /// - /// 读取字符串,但不移动解析器指针 - /// - /// 终点字符 - /// - public string Peek(char terminator, bool includeTerminator = false) - { - var end = IndexOfOrEnd(terminator); - if (includeTerminator) - ++end; - return _str[_pos..end]; - } - - /// - /// 读取字符串,但不移动解析器指针 - /// - /// false时停止 - /// - public string PeekWhen(Func predicate, bool includeTerminator = false) - { - var cur = _pos; - while (!(cur >= _str.Length || !predicate(_str[cur]))) - ++cur; - if (includeTerminator) - ++cur; - return _str[_pos..cur]; - } - - /// - /// 读取字符串,且移动解析器指针,直至空白字符 - /// - public string PeekToWhiteSpace() => PeekWhen(t => !char.IsWhiteSpace(t)); - - /// - /// 查看包含引号的字符串 - /// - public string PeekQuoted() - { - if (IsEnd) - return ""; - - var index = Array.IndexOf(LeftQuotes, Current); - - if (index is -1) - return PeekToWhiteSpace(); - return Peek(RightQuotes[index], true)[..^1]; - } - - #endregion - - #region Read - - /// - /// 读取字符串,且移动解析器指针 - /// - /// 终点字符,将解析器指针指向该字符 - /// 同时读取终点字符,解析器指针指向下一字符 - public string Read(char terminator, bool includeTerminator = false) - { - var start = _pos; - _ = Skip(terminator, includeTerminator); - return _str[start.._pos]; - } - - /// - /// 读取字符串,且移动解析器指针 - /// - /// false时停止 - /// 同时读取终点字符,解析器指针指向下一字符 - public string ReadWhen(Func predicate, bool includeTerminator = false) - { - var start = _pos; - _ = SkipWhen(predicate, includeTerminator); - return _str[start.._pos]; - } - - /// - /// 读取字符串,且移动解析器指针,直至空白字符 - /// - public string ReadToWhiteSpace() => ReadWhen(t => !char.IsWhiteSpace(t)); - - /// - /// 读取字符串的剩余部分 - /// - public string ReadToEnd() - { - var value = _str[_pos..]; - _pos = _str.Length; - return value; - } - - /// - /// 读取包含引号的字符串 - /// - public string ReadQuoted() - { - if (IsEnd) - return ""; - - var index = Array.IndexOf(LeftQuotes, Current); - - if (index is -1) - return ReadToWhiteSpace(); - ++_pos; - return Read(RightQuotes[index], true)[..^1]; - } - - #endregion - - private int IndexOfOrEnd(char value) - { - var r = _str.IndexOf(value, _pos); - return r is -1 ? _str.Length : r; - } -} diff --git a/src/Flandre.Core/Utils/TextUtils.cs b/src/Flandre.Core/Utils/TextUtils.cs deleted file mode 100644 index 1147b66..0000000 --- a/src/Flandre.Core/Utils/TextUtils.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Flandre.Core.Utils; - -internal static class TextUtils -{ - internal static string RemoveString(this string text, string remove) - { - return text.Replace(remove, ""); - } - - internal static string TrimStart(this string source, string value, - StringComparison comparison = StringComparison.Ordinal) - { - if (value == "") - return source; - var valueLength = value.Length; - var startIndex = 0; - while (source.IndexOf(value, startIndex, comparison) == startIndex) - startIndex += valueLength; - - return source[startIndex..]; - } - - internal static string TrimEnd(this string source, string value, - StringComparison comparison = StringComparison.Ordinal) - { - if (value == "") - return source; - var sourceLength = source.Length; - var valueLength = value.Length; - var count = sourceLength; - while (source.LastIndexOf(value, count, comparison) == count - valueLength) - count -= valueLength; - - return source[..count]; - } -} diff --git a/src/Flandre.Framework.Reactive/AppReactiveExtensions.cs b/src/Flandre.Framework.Reactive/AppReactiveExtensions.cs deleted file mode 100644 index 9d96e7d..0000000 --- a/src/Flandre.Framework.Reactive/AppReactiveExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Reactive.Linq; -using Flandre.Core.Events; -using Flandre.Framework.Events; - -namespace Flandre.Framework.Reactive; - -public static class AppReactiveExtensions -{ - private static IObservable OnAppEvent( - Action> add, Action> remove) where TEvent : FlandreEvent - { - return Observable.FromEventPattern, TEvent>(add, remove) - .Select(pattern => pattern.EventArgs); - } - - public static IObservable OnStarting(this FlandreApp app) - { - return OnAppEvent( - add => app.Starting += add, - remove => app.Starting -= remove); - } - - public static IObservable OnReady(this FlandreApp app) - { - return OnAppEvent( - add => app.Ready += add, - remove => app.Ready -= remove); - } - - public static IObservable OnStopped(this FlandreApp app) - { - return OnAppEvent( - add => app.Stopped += add, - remove => app.Stopped -= remove); - } - - public static IObservable OnCommandInvoking(this FlandreApp app) - { - return OnAppEvent( - add => app.CommandInvoking += add, - remove => app.CommandInvoking -= remove); - } - - public static IObservable OnCommandInvoked(this FlandreApp app) - { - return OnAppEvent( - add => app.CommandInvoked += add, - remove => app.CommandInvoked -= remove); - } -} diff --git a/src/Flandre.Framework.Reactive/Flandre.Framework.Reactive.csproj b/src/Flandre.Framework.Reactive/Flandre.Framework.Reactive.csproj deleted file mode 100644 index ebd7cfd..0000000 --- a/src/Flandre.Framework.Reactive/Flandre.Framework.Reactive.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - Flandre.Framework.Reactive - 1.0.0-rc.10 - FlandreDevs,bsdayo - Reactive Extensions (Rx.NET) support for Flandre.Framework. - flandre;rx.net;reactive - MIT - avatar.jpg - - net6.0 - enable - enable - Library - true - CS1591 - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2022-2023 - - - - - - - - - - - - diff --git a/src/Flandre.Framework/AdapterCollection.cs b/src/Flandre.Framework/AdapterCollection.cs deleted file mode 100644 index 27b6292..0000000 --- a/src/Flandre.Framework/AdapterCollection.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Flandre.Core.Common; -using Microsoft.Extensions.DependencyInjection; - -namespace Flandre.Framework; - -/// -/// 适配器集合 -/// -public interface IAdapterCollection -{ - /// - /// 适配器使用的服务 - /// - IServiceCollection Services { get; } - - /// - /// 添加一个适配器 - /// - /// 适配器实例 - IAdapterCollection Add(IAdapter adapter); -} - -internal sealed class AdapterCollection : IAdapterCollection -{ - public IServiceCollection Services { get; } - - internal List Adapters { get; } = new(); - - internal AdapterCollection(IServiceCollection services) - { - Services = services; - } - - public IAdapterCollection Add(IAdapter adapter) - { - Adapters.Add(adapter); - return this; - } -} diff --git a/src/Flandre.Framework/AssemblyInfo.cs b/src/Flandre.Framework/AssemblyInfo.cs deleted file mode 100644 index 9b9171a..0000000 --- a/src/Flandre.Framework/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Flandre.Framework.Tests")] diff --git a/src/Flandre.Framework/Common/Command.cs b/src/Flandre.Framework/Common/Command.cs deleted file mode 100644 index 1057549..0000000 --- a/src/Flandre.Framework/Common/Command.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System.ComponentModel; -using System.Reflection; -using System.Text.RegularExpressions; -using Flandre.Core.Messaging; -using Flandre.Framework.Routing; -using Microsoft.Extensions.Logging; - -namespace Flandre.Framework.Common; - -/// -/// 表示一个指令 -/// -public sealed class Command -{ - /// - /// 指令名 - /// - public string Name { get; } - - /// - /// 指令完整名称(路径) - /// - public string FullName { get; } - - internal List Parameters { get; } = new(); - internal List Options { get; } = new(); - - /// - /// Contains alias command path - /// - internal List Aliases { get; } = new(); - - internal List Shortcuts { get; } = new(); - - /// - /// 调用指令方法的目标。如果不为 null 代表该方法是一个委托,否则为指令方法。 - /// - internal object? InvokeTarget { get; private set; } - - /// - /// 调用指令方法的目标。如果为 null 代表该方法是一个委托,否则为指令方法。 - /// - internal Type? PluginType { get; } - - internal MethodInfo? InnerMethod { get; private set; } - - internal bool IsObsolete { get; set; } - internal string? ObsoleteMessage { get; set; } - internal string? Description { get; set; } - - private readonly CommandNode _currentNode; - - internal Command(CommandNode currentNode, Type? pluginType, string name, string fullName) - { - _currentNode = currentNode; - PluginType = pluginType; - Name = name; - FullName = fullName; - } - - private void InferFromMethod(MethodBase method) - { - Options.Clear(); - Parameters.Clear(); - - foreach (var param in method.GetParameters()) - { - if (param.ParameterType.IsAssignableFrom(typeof(CommandContext))) - continue; - - var description = param.GetCustomAttribute()?.Description; - - if (param.GetCustomAttribute() is { } optAttr) - { - // option - var paramType = param.ParameterType; - - Options.Add(new CommandOption(param.Name!, optAttr.ShortName, paramType, - param.HasDefaultValue // 如果 option 定义了默认值 - ? param.DefaultValue! // 则使用定义的默认值 - // 否则使用 default(T) - : param.ParameterType.IsValueType - ? Activator.CreateInstance(paramType) - : null) { Description = description }); - continue; - } - - // parameter - var paramIsParamArray = param.ParameterType.IsArray && - param.GetCustomAttribute() is not null; - Parameters.Add(new CommandParameter( - param.Name!, - param.ParameterType, - !(paramIsParamArray || param.HasDefaultValue), - param.HasDefaultValue ? param.DefaultValue : null, - paramIsParamArray) { Description = description }); - } - } - - #region FluentAPI - - /// - /// 添加别名 - /// - /// - /// - public Command AddAlias(string aliasPath) - { - Aliases.Add(aliasPath); - return this; - } - - /// - /// 添加字符串匹配快捷方式 - /// - public Command AddShortcut(string shortcut, string target, bool allowArguments = false) - { - Shortcuts.Add(new StringShortcut(shortcut, target, allowArguments)); - return this; - } - - /// - /// 添加正则式快捷方式 - /// - public Command AddShortcut(Regex shortcut, string target) - { - Shortcuts.Add(new RegexShortcut(shortcut, target)); - return this; - } - - internal Command WithAction(MethodInfo methodInfo, object? target = null) - { - InnerMethod = methodInfo; - InvokeTarget = target; - InferFromMethod(InnerMethod); - return this; - } - - /// - /// 添加指令方法 - /// - /// - /// - public Command WithAction(Delegate commandDelegate) - { - return WithAction(commandDelegate.Method, commandDelegate.Target); - } - - /// - /// 添加子指令 - /// - /// - /// - public Command AddSubCommand(string path) - { - return _currentNode.MapCommand(PluginType, path); - } - - #endregion - - internal async Task InvokeAsync( - Plugin? plugin, CommandContext ctx, - CommandParseResult parsed, ILogger logger) - { - if (InnerMethod is null) - return null; - - var args = new List(); - var parsedArgIndex = 0; - var methodParams = InnerMethod.GetParameters(); - foreach (var param in methodParams) - { - if (param.ParameterType != typeof(object) && - param.ParameterType.IsAssignableFrom(typeof(CommandContext))) - { - args.Add(ctx); - continue; - } - - if (parsedArgIndex > parsed.ParsedArguments.Count) - throw new CommandInvokeException("Too many arguments requested."); - var attr = param.GetCustomAttribute(); - if (attr is null) - { - // argument - args.Add(parsed.ParsedArguments[parsedArgIndex]); - parsedArgIndex++; - } - else - { - // option - if (param.Name is null) - throw new CommandInvokeException("Option parameter must have a name."); - if (!parsed.ParsedOptions.TryGetValue(param.Name, out var obj)) - throw new CommandInvokeException($"No such option named {param.Name}."); - args.Add(obj); - } - } - - // void MethodReturnTypeNotResolved() - // { - // logger.LogWarning("无法将指令方法 {PluginType}.{MethodName} 的返回类型转换为可解析的返回类型。", - // plugin.GetType().Name, InnerMethod?.Name); - // } - - var target = plugin ?? InvokeTarget; - if (target is null) - { - logger.LogError("指令 {CommandFullName} 调用失败,非插件指令需要有调用目标。", FullName); - return null; - } - - var cmdResult = InnerMethod?.Invoke(target, args.ToArray()); - var content = cmdResult switch - { - MessageContent mc => mc, - Task mcTask => await mcTask, - ValueTask mcValueTask => await mcValueTask, - - MessageBuilder mb => mb.Build(), - Task mbTask => (MessageContent)await mbTask, - ValueTask mbValueTask => (MessageContent)await mbValueTask, - - Task strTask => (MessageContent)await strTask, - ValueTask strValueTask => (MessageContent)await strValueTask, - - _ => cmdResult?.ToString() is { } val ? (MessageContent)val : null - }; - - return content; - } -} diff --git a/src/Flandre.Framework/Common/CommandContext.cs b/src/Flandre.Framework/Common/CommandContext.cs deleted file mode 100644 index ddc8e8b..0000000 --- a/src/Flandre.Framework/Common/CommandContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Flandre.Core.Common; -using Flandre.Core.Messaging; - -namespace Flandre.Framework.Common; - -/// -/// 指令上下文 -/// -public class CommandContext : MessageContext -{ - /// - /// 所在的 实例 - /// - public FlandreApp App { get; } - - /// - /// 构造实例 - /// - /// - /// - /// - public CommandContext(FlandreApp app, Bot bot, Message message) - : base(bot, message) - { - App = app; - } -} diff --git a/src/Flandre.Framework/Common/CommandExceptions.cs b/src/Flandre.Framework/Common/CommandExceptions.cs deleted file mode 100644 index 1c99385..0000000 --- a/src/Flandre.Framework/Common/CommandExceptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Flandre.Framework.Common; - -/// -/// 指令调用异常 -/// -public sealed class CommandInvokeException : Exception -{ - /// - /// - /// - /// - public CommandInvokeException(string message) : base(message) - { - } -} diff --git a/src/Flandre.Framework/Common/CommandOption.cs b/src/Flandre.Framework/Common/CommandOption.cs deleted file mode 100644 index 6b41d13..0000000 --- a/src/Flandre.Framework/Common/CommandOption.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Flandre.Framework.Common; - -/// -/// 指令选项 -/// -public sealed class CommandOption -{ - /// - /// 选项名称 - /// - public string Name { get; } - - /// - /// 选项短名称 - /// - public char ShortName { get; } - - /// - /// 是否有短名称 - /// - public bool HasShortName { get; } - - /// - /// 选项类型 - /// - public Type Type { get; } - - /// - /// 选项默认值 - /// - public object? DefaultValue { get; } - - /// - /// 选项描述 - /// - public string? Description { get; init; } - - internal CommandOption(string name, char shortName, Type type, object? defaultValue) - { - Name = name; - ShortName = shortName; - HasShortName = shortName != default; - Type = type; - DefaultValue = defaultValue; - } -} diff --git a/src/Flandre.Framework/Common/CommandParameter.cs b/src/Flandre.Framework/Common/CommandParameter.cs deleted file mode 100644 index b8e7da4..0000000 --- a/src/Flandre.Framework/Common/CommandParameter.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Flandre.Framework.Common; - -/// -/// 指令参数 -/// -public sealed class CommandParameter -{ - /// - /// 参数名称 - /// - public string Name { get; } - - /// - /// 参数类型 - /// - public Type Type { get; } - - /// - /// 参数默认值 - /// - public object? DefaultValue { get; } - - /// - /// 参数描述 - /// - public string? Description { get; init; } - - /// - /// 是否被 params 修饰 - /// - public bool IsParamArray { get; } - - /// - /// 是否为必须参数 - /// - public bool IsRequired { get; } - - internal CommandParameter(string name, Type type, bool isRequired, object? defaultValue, bool isParamArray) - { - Name = name; - Type = type; - IsRequired = isRequired; - DefaultValue = isParamArray ? Array.CreateInstance(type.GetElementType()!, 0) : defaultValue; - IsParamArray = isParamArray; - } -} diff --git a/src/Flandre.Framework/Common/CommandParseResult.cs b/src/Flandre.Framework/Common/CommandParseResult.cs deleted file mode 100644 index 46d5646..0000000 --- a/src/Flandre.Framework/Common/CommandParseResult.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Flandre.Framework.Common; - -/// -/// 指令解析结果 -/// -public sealed class CommandParseResult -{ - /// - /// 参数解析结果 - /// - public List ParsedArguments { get; } = new(); - - /// - /// 选项解析结果(选项名-值) - /// - public Dictionary ParsedOptions { get; } = new(); - - internal string? ErrorMessage { get; private set; } - - /// - /// 设置为解析错误,并发送一条提示消息给用户 - /// - /// 错误消息 - /// - public CommandParseResult SetError(string message) - { - ErrorMessage = message; - return this; - } -} diff --git a/src/Flandre.Framework/Common/CommandShortcut.cs b/src/Flandre.Framework/Common/CommandShortcut.cs deleted file mode 100644 index ca2e694..0000000 --- a/src/Flandre.Framework/Common/CommandShortcut.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text.RegularExpressions; -using Flandre.Core.Utils; - -namespace Flandre.Framework.Common; - -internal abstract class CommandShortcut -{ - public string Target { get; } - - public CommandShortcut(string target) - { - Target = target; - } - - public abstract bool TryFormat(string text, [NotNullWhen(true)] out string? resultText); -} - -internal class StringShortcut : CommandShortcut -{ - private readonly string _shortcut; - - private readonly bool _allowArguments; - - public StringShortcut(string shortcut, string target, bool allowArguments) : base(target) - { - _shortcut = shortcut; - _allowArguments = allowArguments; - } - - public StringShortcut(StringShortcutAttribute attr) - : this(attr.StringShortcut, attr.Target, attr.AllowArguments) - { - } - - public override bool TryFormat(string text, [NotNullWhen(true)] out string? resultText) - { - if (_allowArguments) - { - if (text.StartsWith(_shortcut)) - { - resultText = $"{Target} {text.TrimStart(_shortcut)}"; - return true; - } - } - else if (text == _shortcut) - { - resultText = Target; - return true; - } - - resultText = null; - return false; - } -} - -internal class RegexShortcut : CommandShortcut -{ - private readonly Regex _regex; - - public RegexShortcut(Regex regex, string target) : base(target) - { - _regex = regex; - } - - public RegexShortcut(RegexShortcutAttribute attr) - : this(attr.RegexShortcut, attr.Target) - { - } - - public override bool TryFormat(string text, [NotNullWhen(true)] out string? resultText) - { - var match = _regex.Match(text); - if (match.Success) - { - // for (var i = 0; i < match.Groups.Count; i++) - // { - // var group = match.Groups[i]; - // text = text.Replace($"${i + 1}", group.Value); - // } - resultText = _regex.Replace(text, Target); - return true; - } - - resultText = null; - return false; - } -} diff --git a/src/Flandre.Framework/Common/ICommandParser.cs b/src/Flandre.Framework/Common/ICommandParser.cs deleted file mode 100644 index 95907f5..0000000 --- a/src/Flandre.Framework/Common/ICommandParser.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Flandre.Core.Utils; - -namespace Flandre.Framework.Common; - -/// -/// 指令解析器 -/// -public interface ICommandParser -{ - /// - /// 解析指令的参数、选项部分 - /// - /// 当前指令 - /// 当前解析器 - /// 解析结果。如果解析失败,使用 说明。 - CommandParseResult Parse(Command command, StringParser parser); -} diff --git a/src/Flandre.Framework/Common/MiddlewareContext.cs b/src/Flandre.Framework/Common/MiddlewareContext.cs deleted file mode 100644 index 395369a..0000000 --- a/src/Flandre.Framework/Common/MiddlewareContext.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Flandre.Core.Common; -using Flandre.Core.Messaging; -using Flandre.Core.Utils; -using Microsoft.Extensions.DependencyInjection; - -namespace Flandre.Framework.Common; - -/// -/// 中间件上下文,包含当前时刻所需的全部对象 -/// -public sealed class MiddlewareContext : MessageContext -{ - internal readonly IServiceScope ServiceScope; - - /// - /// 所在 实例,包含全局服务 - /// - public FlandreApp App { get; } - - /// - /// 域内服务 - /// - public IServiceProvider Services => ServiceScope.ServiceProvider; - - /// - /// 即将发送的回复 - /// - public MessageContent? Response { get; set; } - - /// - /// 当前所在的指令 - /// - public Command? Command { get; internal set; } - - /// - /// 执行指令产生的异常。如果指令成功执行,则该项为 null - /// - public Exception? Exception { get; internal set; } - - // TODO: 添加 IsFailed 和 FailReason 属性 - // TODO: enum MiddlewareFailReason { None, Exception, MissingArgument, ... } - - private IDictionary? _properties; - - /// - /// 中间件属性,用于在中间件内传递消息 - /// - public IDictionary Properties => _properties ??= new Dictionary(); - - internal StringParser? CommandStringParser { get; set; } - - internal MiddlewareContext(FlandreApp app, Bot bot, Message message, MessageContent? resp) - : base(bot, message) - { - App = app; - ServiceScope = app.Services.CreateScope(); - Response = resp; - } -} diff --git a/src/Flandre.Framework/Common/OptionAttribute.cs b/src/Flandre.Framework/Common/OptionAttribute.cs deleted file mode 100644 index 3405a8b..0000000 --- a/src/Flandre.Framework/Common/OptionAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Flandre.Framework.Common; - -/// -/// 选项 -/// -[AttributeUsage(AttributeTargets.Parameter)] -public sealed class OptionAttribute : Attribute -{ - /// - /// 短名称 - /// - /// 长名称取决于参数名 - public char ShortName { get; init; } -} diff --git a/src/Flandre.Framework/Common/Plugin.cs b/src/Flandre.Framework/Common/Plugin.cs deleted file mode 100644 index 61b45cb..0000000 --- a/src/Flandre.Framework/Common/Plugin.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Flandre.Core.Common; -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Framework.Routing; -using Microsoft.Extensions.Logging; - -namespace Flandre.Framework.Common; - -/// -/// 插件 -/// -public abstract class Plugin -{ - /// - /// 缓存日志类型 - /// - private static Type? _loggerType; - - internal Type LoggerType - { - get - { - _loggerType ??= typeof(ILogger<>).MakeGenericType(GetType()); - return _loggerType; - } - } - - /// - /// 映射指令时调用 - /// - /// - public virtual void OnCommandMapping(ICommandRouteBuilder builder) { } - - /// - /// 加载插件时调用 - /// - /// - public virtual Task OnLoadingAsync() => Task.CompletedTask; - - /// - /// 卸载插件时调用 - /// - /// - public virtual Task OnUnloadingAsync() => Task.CompletedTask; - - /// - /// 处理消息事件 - /// - /// 当前消息上下文 - public virtual Task OnMessageReceivedAsync(MessageContext ctx) => Task.CompletedTask; - - /// - /// 收到拉群邀请 - /// - /// 当前上下文 - /// 拉群邀请事件 - public virtual Task OnGuildInvitedAsync(BotContext ctx, BotGuildInvitedEvent e) => Task.CompletedTask; - - /// - /// 收到入群申请 - /// - /// 当前上下文 - /// 入群申请事件 - public virtual Task OnGuildJoinRequestedAsync(BotContext ctx, BotGuildJoinRequestedEvent e) => Task.CompletedTask; - - /// - /// 收到好友申请 - /// - /// 当前上下文 - /// 好友申请事件 - public virtual Task OnFriendRequestedAsync(BotContext ctx, BotFriendRequestedEvent e) => Task.CompletedTask; -} diff --git a/src/Flandre.Framework/Common/ShortcutAttribute.cs b/src/Flandre.Framework/Common/ShortcutAttribute.cs deleted file mode 100644 index 56df2f6..0000000 --- a/src/Flandre.Framework/Common/ShortcutAttribute.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Text.RegularExpressions; - -namespace Flandre.Framework.Common; - -/// -/// 为指令添加前缀式快捷方式 -/// -[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -public class StringShortcutAttribute : Attribute -{ - /// - /// 字符串匹配快捷方式 - /// - public string StringShortcut { get; } - - /// - /// 目标指令文本 - /// - public string Target { get; } - - /// - /// 允许附加参数 - /// - public bool AllowArguments { get; init; } - - /// - /// 构造特性实例 - /// - /// 字符串匹配快捷方式 - /// 目标指令文本 - public StringShortcutAttribute(string shortcut, string target) - { - StringShortcut = shortcut; - Target = target; - } - - /// - /// 构造特性实例 - /// - /// 字符串匹配快捷方式 - public StringShortcutAttribute(string shortcut) - { - StringShortcut = shortcut; - Target = string.Empty; - } -} - -/// -/// 为指令添加正则式快捷方式 -/// -[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -public class RegexShortcutAttribute : Attribute -{ - /// - /// 正则式快捷方式 - /// - public Regex RegexShortcut { get; } - - /// - /// 目标指令文本 - /// - public string Target { get; } - - /// - /// 构造特性实例 - /// - /// 正则式快捷方式 - /// 目标指令文本 - public RegexShortcutAttribute(string pattern, string target) - { - RegexShortcut = new Regex(pattern); - Target = target; - } - - /// - /// 构造特性实例 - /// - /// 正则式快捷方式 - public RegexShortcutAttribute(string pattern) - { - RegexShortcut = new Regex(pattern); - Target = string.Empty; - } -} diff --git a/src/Flandre.Framework/Common/TypeResolverDelegate.cs b/src/Flandre.Framework/Common/TypeResolverDelegate.cs deleted file mode 100644 index b8c9c58..0000000 --- a/src/Flandre.Framework/Common/TypeResolverDelegate.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Flandre.Framework.Common; - -/// -/// 类型解析器委托 -/// -/// 需要解析的类型 -public delegate bool TypeResolverDelegate(string raw, out T result); - -internal delegate bool TypeResolverDelegate(string raw, [NotNullWhen(true)] out object? result); diff --git a/src/Flandre.Framework/Events/AppReadyEvent.cs b/src/Flandre.Framework/Events/AppReadyEvent.cs deleted file mode 100644 index 584f08e..0000000 --- a/src/Flandre.Framework/Events/AppReadyEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Flandre.Core.Events; - -namespace Flandre.Framework.Events; - -/// -/// 应用就绪事件 -/// -public sealed class AppReadyEvent : FlandreEvent -{ - internal AppReadyEvent() - { - } -} diff --git a/src/Flandre.Framework/Events/AppStartingEvent.cs b/src/Flandre.Framework/Events/AppStartingEvent.cs deleted file mode 100644 index 88fb521..0000000 --- a/src/Flandre.Framework/Events/AppStartingEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Flandre.Core.Events; - -namespace Flandre.Framework.Events; - -/// -/// 应用正在启动事件 -/// -public sealed class AppStartingEvent : FlandreEvent -{ - internal AppStartingEvent() - { - } -} diff --git a/src/Flandre.Framework/Events/AppStoppedEvent.cs b/src/Flandre.Framework/Events/AppStoppedEvent.cs deleted file mode 100644 index a125feb..0000000 --- a/src/Flandre.Framework/Events/AppStoppedEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Flandre.Core.Events; - -namespace Flandre.Framework.Events; - -/// -/// 应用退出事件 -/// -public sealed class AppStoppedEvent : FlandreEvent -{ - internal AppStoppedEvent() - { - } -} diff --git a/src/Flandre.Framework/Events/CommandInvokedEvent.cs b/src/Flandre.Framework/Events/CommandInvokedEvent.cs deleted file mode 100644 index 10bfae9..0000000 --- a/src/Flandre.Framework/Events/CommandInvokedEvent.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Framework.Common; - -namespace Flandre.Framework.Events; - -/// -/// 触发指令事件 -/// -public class CommandInvokedEvent : FlandreEvent -{ - /// - /// 将触发的指令 - /// - public Command Command { get; } - - /// - /// 当前消息 - /// - public Message Message { get; } - - /// - /// 触发失败后抛出的异常 - /// - public Exception? Exception { get; } - - /// - /// 是否触发成功 - /// - public bool IsSucceeded => Exception is null; - - /// - /// 触发成功后将发送的消息 - /// - public MessageContent? Response { get; } - - /// - /// 用户 ID - /// - /// 等同于 .Sender.UserId - public string UserId => Message.Sender.UserId; - - /// - /// 群组 ID - /// - /// 等同于 .GuildId - public string? GuildId => Message.GuildId; - - /// - /// 频道 ID - /// - /// 等同于 .ChannelId - public string? ChannelId => Message.ChannelId; - - internal CommandInvokedEvent(Command command, Message message, Exception? exception, MessageContent? resp) - { - Command = command; - Message = message; - Exception = exception; - Response = resp; - } -} diff --git a/src/Flandre.Framework/Events/CommandInvokingEvent.cs b/src/Flandre.Framework/Events/CommandInvokingEvent.cs deleted file mode 100644 index 997a6f3..0000000 --- a/src/Flandre.Framework/Events/CommandInvokingEvent.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Flandre.Core.Events; -using Flandre.Core.Messaging; -using Flandre.Framework.Common; - -namespace Flandre.Framework.Events; - -public class CommandInvokingEvent : FlandreEvent -{ - public Command Command { get; } - - /// - /// 当前消息 - /// - public Message Message { get; } - - /// - /// 用户 ID,等同于 Message.Sender.UserId - /// - public string UserId => Message.Sender.UserId; - - /// - /// 群组 ID,等同于 Message.GuildId - /// - public string? GuildId => Message.GuildId; - - /// - /// 频道 ID,等同于 Message.ChannelId - /// - public string? ChannelId => Message.ChannelId; - - internal bool IsCancelled { get; private set; } - - internal CommandInvokingEvent(Command command, Message message) - { - Command = command; - Message = message; - } - - public void Cancel() => IsCancelled = true; -} diff --git a/src/Flandre.Framework/Extensions/FlandreAppExtensions.cs b/src/Flandre.Framework/Extensions/FlandreAppExtensions.cs deleted file mode 100644 index f2e524b..0000000 --- a/src/Flandre.Framework/Extensions/FlandreAppExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Flandre.Core.Common; - -// ReSharper disable once CheckNamespace -namespace Flandre.Framework; - -/// -/// 的扩展方法 -/// -public static class FlandreAppExtensions -{ - /// - /// 运行应用实例,并自动注册内置中间件 - /// - public static Task StartWithDefaultsAsync(this FlandreApp app, CancellationToken cancellationToken = default) - { - app.UseCommandSession(); - app.UseCommandParser(); - app.UseCommandInvoker(); - return app.StartAsync(cancellationToken); - } - - /// - /// 设置群组代理 (主 bot) - /// - /// 实例 - /// 平台 - /// 群组 ID - /// - public static void SetGuildAssignee(this FlandreApp app, string platform, string guildId, string botId) - { - app.GuildAssignees.AddOrUpdate($"{platform}:{guildId}", botId, (_, _) => botId); - } - - /// - /// 检查群组是否已被代理(已设置主 bot) - /// - /// 实例 - /// 平台 - /// - public static bool IsGuildAssigned(this FlandreApp app, string platform, string botId) - { - return app.GuildAssignees.ContainsKey($"{platform}:{botId}"); - } -} diff --git a/src/Flandre.Framework/Extensions/ServiceCollectionExtensions.cs b/src/Flandre.Framework/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index eeffc28..0000000 --- a/src/Flandre.Framework/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -// ReSharper disable once CheckNamespace -namespace Flandre.Framework; - -/// -/// 服务集合扩展方法 -/// -public static class ServiceCollectionExtensions -{ - /// - /// 配置 - /// - /// - /// - /// - public static IServiceCollection ConfigureFlandreApp(this IServiceCollection services, - IConfiguration configuration) - { - return services.Configure(configuration); - } - - /// - /// 配置 - /// - /// - /// - /// - public static IServiceCollection ConfigureFlandreApp(this IServiceCollection services, - Action action) - { - return services.Configure(action); - } -} diff --git a/src/Flandre.Framework/Extensions/SessionExtensions.cs b/src/Flandre.Framework/Extensions/SessionExtensions.cs deleted file mode 100644 index 4cdf43d..0000000 --- a/src/Flandre.Framework/Extensions/SessionExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Flandre.Core.Messaging; -using Flandre.Framework.Utils; - -// ReSharper disable once CheckNamespace -namespace Flandre.Framework.Common; - -/// -/// 会话扩展方法 -/// -public static class SessionExtensions -{ - /// - /// 截获用户发送的下一条消息 - /// - /// 当前指令上下文 - /// 超时时间 - /// 用户发送的下一条消息,如果超时则返回 - public static Task StartSessionAsync(this CommandContext ctx, TimeSpan timeout) - { - var cts = new CancellationTokenSource(timeout); - var tcs = new TaskCompletionSource(); - - var mark = ctx.GetUserMark(); - - cts.Token.Register(() => - { - ctx.App.CommandSessions.TryRemove(mark, out _); - tcs.TrySetResult(null); - }); - - ctx.App.CommandSessions[mark] = tcs; - - return tcs.Task; - } -} diff --git a/src/Flandre.Framework/Flandre.Framework.csproj b/src/Flandre.Framework/Flandre.Framework.csproj deleted file mode 100644 index 1305900..0000000 --- a/src/Flandre.Framework/Flandre.Framework.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - Flandre.Framework - 1.0.0-rc.11 - FlandreDevs,bsdayo - 现代化、跨平台的聊天机器人框架,一次编写,多处运行。 - bot;chatbot;flandre;framework - MIT - avatar.jpg - README.NuGet.md - - net6.0 - enable - enable - Library - preview - true - - https://github.com/FlandreDevs/Flandre - https://github.com/FlandreDevs/Flandre.git - git - FlandreDevs (C) 2022-2023 - - - - - - - - - - - - - - - - - diff --git a/src/Flandre.Framework/FlandreApp.cs b/src/Flandre.Framework/FlandreApp.cs deleted file mode 100644 index 65f3199..0000000 --- a/src/Flandre.Framework/FlandreApp.cs +++ /dev/null @@ -1,266 +0,0 @@ -using System.Collections.Concurrent; -using Flandre.Core.Common; -using Flandre.Core.Messaging; -using Flandre.Framework.Common; -using Flandre.Framework.Events; -using Flandre.Framework.Internal; -using Flandre.Framework.Routing; -using Flandre.Framework.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Flandre.Framework; - -/// -/// Flandre 应用 -/// -public sealed partial class FlandreApp : IHost, ICommandRouteBuilder, IAsyncDisposable -{ - private readonly IHost _hostApp; - private readonly IOptionsMonitor _appOptions; - private readonly List _adapters; - private readonly List _pluginTypes; - private readonly List, Task>> _middleware = new(); - private readonly List _bots = new(); - private bool _eventsSubscribedOnce; - private bool _isStopped = true; - - /// - /// 所有机器人实例 - /// - public IEnumerable Bots => _bots.AsReadOnly(); - - /// - /// 服务 - /// - public IServiceProvider Services => _hostApp.Services; - - /// - /// 日志 - /// - public ILogger Logger { get; } - - /// - /// 应用属性,用于在中间件内传递消息 - /// - public IDictionary Properties { get; } = new Dictionary(); - - internal ConcurrentDictionary GuildAssignees { get; } = new(); - internal ConcurrentDictionary> CommandSessions { get; } = new(); - - /// - /// 创建构造器 - /// - /// - /// - public static FlandreAppBuilder CreateBuilder(string[]? args = null) => new(args); - - /// - /// 创建构造器 - /// - /// - /// - public static FlandreAppBuilder CreateBuilder(HostApplicationBuilderSettings? settings) => new(settings); - - internal FlandreApp(IHost hostApp, List pluginTypes, List adapters) - { - _hostApp = hostApp; - _appOptions = _hostApp.Services.GetRequiredService>(); - _pluginTypes = pluginTypes; - _adapters = adapters; - - Logger = Services.GetRequiredService>(); - - foreach (var adapter in _adapters) - _bots.AddRange(adapter.Bots); - } - - private async Task LoadAllPluginsAsync() - { - foreach (var pluginType in _pluginTypes) - { - using var scope = Services.CreateScope(); - var loadCtx = new PluginCommandLoader(pluginType, scope.ServiceProvider); - var plugin = (Plugin)scope.ServiceProvider.GetRequiredService(pluginType); - - loadCtx.LoadFromAttributes(); - // Fluent API can override attributes - await plugin.OnLoadingAsync(); - - loadCtx.LoadCommandAliases(); - loadCtx.LoadCommandShortcuts(); - } - } - - private async Task UnloadAllPluginsAsync() - { - foreach (var pluginType in _pluginTypes) - { - using var scope = Services.CreateScope(); - var plugin = (Plugin)scope.ServiceProvider.GetRequiredService(pluginType); - await plugin.OnUnloadingAsync(); - } - - Services.GetRequiredService().Reset(); - } - - private void SubscribeEvents() - { - if (_eventsSubscribedOnce) return; - _eventsSubscribedOnce = true; - - void WithCatch(Type pluginType, Func subscriber, string? eventName = null) => Task.Run(async () => - { - try - { - using var scope = Services.CreateScope(); - var plugin = (Plugin)scope.ServiceProvider.GetRequiredService(pluginType); - await subscriber.Invoke(plugin); - } - catch (Exception e) - { - var logger = Services.GetRequiredService().CreateLogger(pluginType); - logger.LogError(e, "Error occurred while handling {EventName}", eventName ?? "event"); - } - }); - - foreach (var bot in _bots) - { - bot.MessageReceived += (_, e) => Task.Run(async () => - { - var middlewareCtx = new MiddlewareContext(this, bot, e.Message, null); - await ExecuteMiddlewareAsync(middlewareCtx, 0); // Wait for all middleware's execution - middlewareCtx.ServiceScope.Dispose(); - if (middlewareCtx.Exception is { } ex) - Logger.LogError(ex, "Error occurred while invoking command {CommandName}", - middlewareCtx.Command?.FullName); - if (middlewareCtx.Response is not null) - await bot.SendMessageAsync(e.Message, middlewareCtx.Response); - }); - - var ctx = new BotContext(bot); - - foreach (var pluginType in _pluginTypes) - { - bot.GuildInvited += (_, e) => WithCatch(pluginType, - plugin => plugin.OnGuildInvitedAsync(ctx, e), - nameof(bot.GuildInvited)); - - bot.GuildJoinRequested += (_, e) => WithCatch(pluginType, - plugin => plugin.OnGuildJoinRequestedAsync(ctx, e), - nameof(bot.FriendRequested)); - - bot.FriendRequested += (_, e) => WithCatch(pluginType, - plugin => plugin.OnFriendRequestedAsync(ctx, e), - nameof(bot.FriendRequested)); - } - } - - // Subscribe bots' logging event - foreach (var adapter in _adapters) - { - var adapterType = adapter.GetType(); - foreach (var bot in adapter.Bots) - bot.Logging += (_, e) => - Services.GetRequiredService() - .CreateLogger(adapterType.FullName ?? adapterType.Name) - // ReSharper disable once TemplateIsNotCompileTimeConstantProblem - .Log((LogLevel)e.LogLevel, e.LogMessage); - } - - Logger.LogDebug("All bot events subscribed"); - } - - /// - /// 按顺序执行中间件,遵循洋葱模型 - /// - /// 中间件上下文 - /// 中间件索引 - private async Task ExecuteMiddlewareAsync(MiddlewareContext ctx, int index) - { - try - { - if (_middleware.Count < index + 1) - return; - await _middleware[index].Invoke(ctx, () => ExecuteMiddlewareAsync(ctx, index + 1)); - } - catch (Exception e) - { - Logger.LogError(e, "Error occurred while processing middleware {MiddlewareName}", - _middleware[index].Method.Name); - } - } - - /// - /// 在最内层插入异步中间件 - /// - /// 中间件方法 - public FlandreApp Use(Func, Task> middlewareAction) - { - _middleware.Add(middlewareAction); - return this; - } - - /// - /// 运行应用实例 - /// - public async Task StartAsync(CancellationToken cancellationToken = default) - { - _isStopped = false; - Starting?.Invoke(this, new AppStartingEvent()); - Logger.LogDebug("Starting app..."); - - await LoadAllPluginsAsync(); - SubscribeEvents(); - - UsePluginMessageHandler(); - - await Task.WhenAll(_adapters.Select(adapter => adapter.StartAsync()).ToArray()); - await _hostApp.StartAsync(cancellationToken); - - var cmdService = Services.GetRequiredService(); - - Logger.LogInformation("App started"); - Logger.LogDebug("Total {AdapterCount} adapters, {BotCount} bots", _adapters.Count, _bots.Count); - Logger.LogDebug( - "Total {PluginCount} plugins, {CommandCount} commands, {StringShortcutCount} string shortcuts, {RegexShortcutCount} regex shortcuts, {MiddlewareCount} middleware", - _pluginTypes.Count, cmdService.RootNode.CountCommands(), - cmdService.StringShortcuts.Count, cmdService.RegexShortcuts.Count, _middleware.Count); - Ready?.Invoke(this, new AppReadyEvent()); - } - - /// - /// 停止应用实例 - /// - public async Task StopAsync(CancellationToken cancellationToken = default) - { - await UnloadAllPluginsAsync(); - await Task.WhenAll(_adapters.Select(adapter => adapter.StopAsync()).ToArray()); - await _hostApp.StopAsync(cancellationToken); - Logger.LogInformation("App stopped"); - Stopped?.Invoke(this, new AppStoppedEvent()); - _isStopped = true; - } - - /// - /// 停止应用实例并释放资源 - /// - public void Dispose() - { - if (!_isStopped) - StopAsync().GetAwaiter().GetResult(); - _hostApp.Dispose(); - } - - /// - /// 停止应用实例并释放资源 - /// - public async ValueTask DisposeAsync() - { - if (!_isStopped) - await StopAsync(); - _hostApp.Dispose(); - } -} diff --git a/src/Flandre.Framework/FlandreAppBuilder.cs b/src/Flandre.Framework/FlandreAppBuilder.cs deleted file mode 100644 index e64524f..0000000 --- a/src/Flandre.Framework/FlandreAppBuilder.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Flandre.Core.Common; -using Flandre.Framework.Common; -using Flandre.Framework.Internal; -using Flandre.Framework.Services; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Flandre.Framework; - -/// -/// 构建器 -/// -public sealed class FlandreAppBuilder -{ - private readonly HostApplicationBuilder _hostAppBuilder; - private readonly AdapterCollection _adapterCollection; - private readonly PluginCollection _pluginCollection; - - /// - /// 插件集合 - /// - public IAdapterCollection Adapters => _adapterCollection; - - /// - /// 插件集合 - /// - public IPluginCollection Plugins => _pluginCollection; - - /// - /// 全局服务 - /// - public IServiceCollection Services => _hostAppBuilder.Services; - - /// - /// 配置 - /// - public ConfigurationManager Configuration => _hostAppBuilder.Configuration; - - /// - /// 环境 - /// - public IHostEnvironment Environment => _hostAppBuilder.Environment; - - /// - /// 日志 - /// - public ILoggingBuilder Logging => _hostAppBuilder.Logging; - - /// - /// 配置容器 - /// - /// - /// - /// - public void ConfigureContainer( - IServiceProviderFactory factory, Action? configure = null) - where TContainerBuilder : notnull - => _hostAppBuilder.ConfigureContainer(factory, configure); - - internal FlandreAppBuilder(string[]? args = null) - : this(new HostApplicationBuilderSettings { Args = args }) - { - } - - internal FlandreAppBuilder(HostApplicationBuilderSettings? settings) - { - _hostAppBuilder = new HostApplicationBuilder(settings); - _adapterCollection = new AdapterCollection(Services); - _pluginCollection = new PluginCollection(Services, Configuration); - AddInfrastructure(); - } - - private void AddInfrastructure() - { - Services.AddSingleton(new CommandService()); - } - - /// - /// 添加插件 - /// - /// 插件类型 - [Obsolete("FlandreAppBuilder.AddPlugin() is obsoleted. Use FlandreAppBuilder.Plugins.Plugins.Add() instead.")] - public FlandreAppBuilder AddPlugin() where TPlugin : Plugin - { - Plugins.Add(); - return this; - } - - /// - /// - /// - /// - /// - [Obsolete("FlandreAppBuilder.AddPlugin() is obsoleted. Use FlandreAppBuilder.Plugins.Add() instead.")] - public FlandreAppBuilder AddPlugin(IConfiguration configuration) - where TPlugin : Plugin where TPluginOptions : class - { - Plugins.Add(configuration); - return this; - } - - /// - /// - /// - /// - /// - [Obsolete("FlandreAppBuilder.AddPlugin() is obsoleted. Use FlandreAppBuilder.Plugins.Add() instead.")] - public FlandreAppBuilder AddPlugin(Action action) - where TPlugin : Plugin where TPluginOptions : class - { - Plugins.Add(action); - return this; - } - - /// - /// 添加机器人适配器 - /// - /// - /// - [Obsolete("FlandreAppBuilder.AddAdapter() is obsoleted. Use FlandreAppBuilder.Adapters.Add() instead.")] - public FlandreAppBuilder AddAdapter(IAdapter adapter) - { - _adapterCollection.Add(adapter); - return this; - } - - /// - /// 构建 实例 - /// - public FlandreApp Build() - { - Services.TryAddSingleton(); - var app = new FlandreApp(_hostAppBuilder.Build(), - _pluginCollection.PluginTypes, - _adapterCollection.Adapters); - return app; - } -} diff --git a/src/Flandre.Framework/FlandreAppEvents.cs b/src/Flandre.Framework/FlandreAppEvents.cs deleted file mode 100644 index a026b8b..0000000 --- a/src/Flandre.Framework/FlandreAppEvents.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Flandre.Core.Events; -using Flandre.Framework.Events; - -namespace Flandre.Framework; - -/// -/// 应用事件处理 -/// -/// -/// -/// -public delegate void AppEventHandler(FlandreApp app, TEvent e) where TEvent : FlandreEvent; - -public sealed partial class FlandreApp -{ - /// - /// 应用正在启动 - /// - public event AppEventHandler? Starting; - - /// - /// 应用就绪 - /// - public event AppEventHandler? Ready; - - /// - /// 应用已经退出 - /// - public event AppEventHandler? Stopped; - - /// - /// 应用正在触发指令 - /// - public event AppEventHandler? CommandInvoking; - - /// - /// 应用触发了指令 - /// - public event AppEventHandler? CommandInvoked; -} diff --git a/src/Flandre.Framework/FlandreAppOptions.cs b/src/Flandre.Framework/FlandreAppOptions.cs deleted file mode 100644 index 0b7c35f..0000000 --- a/src/Flandre.Framework/FlandreAppOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Flandre.Framework; - -/// -/// 应用配置 -/// -public sealed class FlandreAppOptions -{ - /// - /// 全局指令前缀 - /// - public string CommandPrefix { get; set; } = string.Empty; - - /// - /// 在用户调用指令时,不进行“指令未找到”提示 - /// - public bool IgnoreUndefinedCommand { get; set; } = false; -} diff --git a/src/Flandre.Framework/Internal/DefaultCommandParser.cs b/src/Flandre.Framework/Internal/DefaultCommandParser.cs deleted file mode 100644 index 2fc850a..0000000 --- a/src/Flandre.Framework/Internal/DefaultCommandParser.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System.Collections; -using Flandre.Core.Utils; -using Flandre.Framework.Common; -using Flandre.Framework.Services; - -namespace Flandre.Framework.Internal; - -internal class DefaultCommandParser : ICommandParser -{ - private readonly CommandService _service; - - public DefaultCommandParser(CommandService service) - { - _service = service; - } - - /// - /// 解析指令文本的参数及选项部分 - /// - /// 解析目标指令 - /// 包含参数及选项部分的 - /// 解析结果 - public CommandParseResult Parse(Command command, StringParser parser) - { - var result = new CommandParseResult(); - - var argIndex = 0; - var providedArgs = new List(); - - while (!parser.IsEnd) - { - var peek = parser.SkipWhiteSpaces().PeekToWhiteSpace(); - - // option (full) - if (peek.StartsWith("--", StringComparison.OrdinalIgnoreCase)) - { - var optName = parser.ReadToWhiteSpace().TrimStart('-'); - - // 例: `--no-check` 将名为 `check` 的选项设置为 false - // 如果者类型不是 bool,无事发生 - var optNo = false; - - if (optName.Length > 3 && optName.StartsWith("no-", StringComparison.OrdinalIgnoreCase)) - { - optName = optName[3..]; - optNo = true; - } - - var option = command.Options.FirstOrDefault(opt => opt.Name == optName); - if (option is null) - { - return result.SetError($"未知选项:{optName}"); - } - - if (option.Type == typeof(bool)) - { - result.ParsedOptions[option.Name] = !optNo; - } - else - { - if (_service.TryParseArgumentValue(option.Type, - parser.SkipWhiteSpaces().ReadToWhiteSpace(), out var obj)) - result.ParsedOptions[option.Name] = obj; - else - return result.SetError(TypeNotMatch(option, _service)); - } - } - else if (peek.StartsWith('-')) // option (short) - { - var opts = parser.ReadToWhiteSpace().TrimStart('-'); - - parser.SkipWhiteSpaces(); - - // 逐字符读取短选项,最后一个如果是非 bool 选项就读取一个参数给它,前面的全部赋值为 true - for (var i = 0; i < opts.Length; i++) - { - var optName = opts[i]; - var option = command.Options.FirstOrDefault(opt => opt.HasShortName && opt.ShortName == optName); - if (option is null) - { - return result.SetError($"未知选项:{optName}"); - } - - if (option.Type == typeof(bool)) - result.ParsedOptions[option.Name] = true; - else - { - // 由于只能赋值给最后一个短选项,前面的必须为 bool - if (i < opts.Length - 1) - return result.SetError(TypeNotMatch(option, _service)); - - var nextArg = parser.ReadToWhiteSpace(); - - if (option.Type == typeof(string)) - result.ParsedOptions[option.Name] = parser.ReadQuoted(); - else if (_service.TryParseArgumentValue(option.Type, nextArg, out var obj)) - result.ParsedOptions[option.Name] = obj; - else - return result.SetError(TypeNotMatch(option, _service)); - } - } - } - else // argument - { - if (argIndex >= command.Parameters.Count) - { - result.SetError("参数过多,请检查指令格式。"); - } - - var param = command.Parameters[argIndex]; - - if (param.IsParamArray) - { - var elemType = param.Type.GetElementType()!; - var list = new ArrayList(); - - while (!parser.IsEnd && - _service.TryParseArgumentValue(elemType, - elemType == typeof(string) ? parser.PeekQuoted() : parser.PeekToWhiteSpace(), - out var obj)) - { - list.Add(obj); - parser.ReadToWhiteSpace(); - parser.SkipWhiteSpaces(); - } - - var arr = Array.CreateInstance(elemType, list.Count); - list.CopyTo(arr); - result.ParsedArguments.Add(arr); - } - else if (param.Type == typeof(string)) - result.ParsedArguments.Add(parser.ReadQuoted()); - else if (_service.TryParseArgumentValue(param.Type, parser.ReadToWhiteSpace(), out var obj)) - result.ParsedArguments.Add(obj); - else - return result.SetError(TypeNotMatch(param, _service)); - - providedArgs.Add(param.Name); - ++argIndex; - } - } - - // 默认值 - // 由于禁止在必选参数前添加可选参数,可以简单地用索引 - foreach (var param in command.Parameters) - { - var provided = providedArgs.Contains(param.Name); - if (param.IsRequired && !provided) - { - return result.SetError($"参数 {param.Name} 缺失。"); - } - - if (param.IsRequired || provided) - continue; - result.ParsedArguments.Add(param.DefaultValue!); - } - - foreach (var opt in command.Options) - result.ParsedOptions.TryAdd(opt.Name, opt.DefaultValue); - - return result; - } - - private static string TypeNotMatch(CommandOption option, CommandService service) - { - return $"选项 {option.Name} 类型错误,应提供一个{service.GetTypeFriendlyName(option.Type)}。"; - } - - private static string TypeNotMatch(CommandParameter param, CommandService service) - { - return $"参数 {param.Name} 类型错误,应提供一个{service.GetTypeFriendlyName(param.Type)}。"; - } -} diff --git a/src/Flandre.Framework/Internal/PluginCommandLoader.cs b/src/Flandre.Framework/Internal/PluginCommandLoader.cs deleted file mode 100644 index 8dbd892..0000000 --- a/src/Flandre.Framework/Internal/PluginCommandLoader.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.ComponentModel; -using System.Reflection; -using Flandre.Framework.Common; -using Flandre.Framework.Routing; -using Flandre.Framework.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Flandre.Framework.Internal; - -/// -/// 读取指令的上下文 -/// -internal sealed class PluginCommandLoader -{ - private readonly CommandService _cmdService; - private readonly Type _pluginType; - private readonly ILogger _logger; - - /// 如果为 null,代表将要加载一个闭包 - /// - internal PluginCommandLoader(Type pluginType, IServiceProvider services) - { - _pluginType = pluginType; - _cmdService = services.GetRequiredService(); - _logger = services.GetRequiredService>(); - } - - #region Internal Processing - - internal void LoadFromAttributes() - { - foreach (var method in _pluginType.GetMethods()) - { - _logger.LogTrace("正在加载方法 {PluginType}.{MethodName}", _pluginType, method.Name); - var cmdAttr = method.GetCustomAttribute(); - if (cmdAttr is null) - continue; - - var cmd = _cmdService.RootNode.MapCommand(_pluginType, cmdAttr.FullName ?? method.Name) - .WithAction(method); - - foreach (var alias in cmdAttr.Aliases) - cmd.AddAlias(alias); - - cmd.Shortcuts.AddRange(method.GetCustomAttributes() - .Select(attr => new StringShortcut(attr))); - cmd.Shortcuts.AddRange(method.GetCustomAttributes() - .Select(attr => new RegexShortcut(attr))); - - // cmd.StringShortcuts = method.GetCustomAttributes() - // .Select(attr => attr.StringShortcut).ToList(); - // cmd.RegexShortcuts = method.GetCustomAttributes() - // .Select(attr => attr.RegexShortcut).ToList(); - - var obsoleteAttr = method.GetCustomAttribute(); - cmd.IsObsolete = obsoleteAttr is not null; - cmd.ObsoleteMessage = obsoleteAttr?.Message; - cmd.Description = method.GetCustomAttribute()?.Description; - } - } - - internal void LoadCommandAliases() - { - var toBeAdded = new Dictionary(); - - void LoadNodeAliases(CommandNode node) - { - if (node.HasCommand) - foreach (var alias in node.Command!.Aliases) - toBeAdded[alias] = node.Command; - - foreach (var (_, subNode) in node.SubNodes) - LoadNodeAliases(subNode); - } - - LoadNodeAliases(_cmdService.RootNode); - - foreach (var (alias, cmd) in toBeAdded) - { - var currentNode = _cmdService.RootNode; - var segments = alias.Split('.', - StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - for (var i = 0; i < segments.Length; i++) - { - currentNode = currentNode.SubNodes.TryGetValue(segments[i], out var nextNode) - ? nextNode - : currentNode.SubNodes[segments[i]] = new CommandNode( - string.Join('.', segments[..(i + 1)])); - - if (i == segments.Length - 1) - { - currentNode.Command = cmd; - currentNode.IsAlias = true; - } - } - } - } - - internal void LoadCommandShortcuts() - { - void LoadNodeShortcuts(CommandNode node) - { - if (node.HasCommand) - { - foreach (var shortcut in node.Command!.Shortcuts) - { - switch (shortcut) - { - case StringShortcut strShortcut: - _cmdService.StringShortcuts[strShortcut] = node.Command; - break; - - case RegexShortcut regShortcut: - _cmdService.RegexShortcuts[regShortcut] = node.Command; - break; - } - } - // foreach (var strShortcut in node.Command!.StringShortcuts) - // stringShortcuts[strShortcut] = node.Command; - // foreach (var regexShortcut in node.Command!.RegexShortcuts) - // regexShortcuts[regexShortcut] = node.Command; - } - - foreach (var (_, subNode) in node.SubNodes) - LoadNodeShortcuts(subNode); - } - - LoadNodeShortcuts(_cmdService.RootNode); - } - - #endregion -} diff --git a/src/Flandre.Framework/InternalMiddlewares.cs b/src/Flandre.Framework/InternalMiddlewares.cs deleted file mode 100644 index 35745fd..0000000 --- a/src/Flandre.Framework/InternalMiddlewares.cs +++ /dev/null @@ -1,257 +0,0 @@ -using Flandre.Core.Messaging; -using Flandre.Core.Messaging.Segments; -using Flandre.Core.Utils; -using Flandre.Framework.Common; -using Flandre.Framework.Events; -using Flandre.Framework.Services; -using Flandre.Framework.Utils; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Flandre.Framework; - -public sealed partial class FlandreApp -{ - private bool CheckMiddlewareUsed(string methodName) - { - var symbol = $"__Flandre_{methodName[3..]}_Middleware_Used"; - if (Properties.ContainsKey(symbol)) - return true; - - // not used - Properties[symbol] = true; - return false; - } - - private FlandreApp UsePluginMessageHandler() - { - if (CheckMiddlewareUsed(nameof(UsePluginMessageHandler))) - return this; - - Use(async (ctx, next) => - { - _pluginTypes.ForEach(p => - { - // create a new scope instead of using ctx scope - using var scope = Services.CreateScope(); - ((Plugin)scope.ServiceProvider.GetRequiredService(p)).OnMessageReceivedAsync(ctx); - }); - await next(); - }); - return this; - } - - /// - /// 使用群组 assignee 检查中间件 - /// - public FlandreApp UseAssigneeChecker() - { - if (CheckMiddlewareUsed(nameof(UseAssigneeChecker))) - return this; - - Use(async (ctx, next) => - { - var segment = ctx.Message.Content.Segments.FirstOrDefault(); - if (segment is AtSegment ats) - { - if (ats.UserId == ctx.SelfId) - await next(); - } - // 如果没找到群组的 assignee - else if (!GuildAssignees.TryGetValue($"{ctx.Platform}:{ctx.GuildId}", out var assignee)) - { - await next(); - } - // 如果找到了群组的 assignee,且是自己 - else if (ctx.SelfId == assignee) - { - await next(); - } - }); - return this; - } - - /// - /// 添加指令会话中间层 - /// - /// 用于指令等待下一条语句 - /// - public FlandreApp UseCommandSession() - { - if (CheckMiddlewareUsed(nameof(UseCommandSession))) - return this; - - Use(async (ctx, next) => - { - var mark = ctx.GetUserMark(); - if (CommandSessions.TryGetValue(mark, out var tcs)) - { - CommandSessions.TryRemove(mark, out _); - tcs.TrySetResult(ctx.Message); - } - else - { - await next(); - } - }); - return this; - } - - /// - /// 添加指令解析中间层 - /// - /// 解析 并得到 - /// - public FlandreApp UseCommandParser() - { - if (CheckMiddlewareUsed(nameof(UseCommandParser))) - return this; - - Use(async (ctx, next) => - { - ctx.Command = ParseCommand(ctx); - await next(); - }); - - return this; - - Command? ParseCommand(MiddlewareContext ctx) - { - // 1. 检查 StringShortcut 中是否有匹配项 - // 2. 检查 RegexShortcut 中是否有匹配项 - // 3. 常规匹配指令 - - var cmdService = Services.GetRequiredService(); - - var commandStr = ctx.Message.GetText().Trim(); - - foreach (var strShortcut in cmdService.StringShortcuts.Keys) - { - if (!strShortcut.TryFormat(commandStr, out var result)) - continue; - ctx.CommandStringParser = new StringParser(result); - return cmdService.StringShortcuts[strShortcut]; - } - - foreach (var regShortcut in cmdService.RegexShortcuts.Keys) - { - if (!regShortcut.TryFormat(commandStr, out var result)) - continue; - ctx.CommandStringParser = new StringParser(result); - return cmdService.RegexShortcuts[regShortcut]; - } - - var commandPrefix = _appOptions.CurrentValue.CommandPrefix; - if (commandStr == commandPrefix) - return null; - - var parser = ctx.CommandStringParser = new StringParser(commandStr); - - var root = parser.SkipWhiteSpaces().Read(' '); - - if (!string.IsNullOrWhiteSpace(commandPrefix) - && !root.StartsWith(commandPrefix)) - return null; - - var path = new List(); - var current = root.TrimStart(commandPrefix); - var node = cmdService.RootNode; - var temp = false; - parser.SkipWhiteSpaces(); - - do - { - var cur = current; - path.Add(cur); - if (node.SubNodes.Keys.FirstOrDefault(k => - cur.Equals(k, StringComparison.OrdinalIgnoreCase)) is { } key) - { - node = node.SubNodes[key]; - if (temp) - parser.Read(' '); - else - temp = true; - current = parser.SkipWhiteSpaces().Peek(' '); - } - else - { - break; - } - } while (!parser.SkipWhiteSpaces().IsEnd); - - if (node.HasCommand) - return node.Command; - - if (!string.IsNullOrWhiteSpace(commandPrefix) && - !_appOptions.CurrentValue.IgnoreUndefinedCommand) - ctx.Response = $"未找到指令:{string.Join('.', path)}。"; - return null; - } - } - - /// - /// 添加指令触发中间层 - /// - /// 触发 并得到 - /// - public FlandreApp UseCommandInvoker() - { - if (CheckMiddlewareUsed(nameof(UseCommandInvoker))) - return this; - - Use(async (ctx, next) => - { - ctx.Response = await InvokeCommand(ctx) ?? ctx.Response; - await next(); - }); - - return this; - - async Task InvokeCommand(MiddlewareContext ctx) - { - if (ctx.Command is null || ctx.CommandStringParser is null) - return null; - - var cmdParser = Services.GetRequiredService(); - - var parseResult = cmdParser.Parse(ctx.Command, ctx.CommandStringParser); - - if (parseResult.ErrorMessage is not null) - return parseResult.ErrorMessage; - - // ctx.Service is a service scope - // 如果 plugin 是 null,那么这个指令方法是个闭包 - var plugin = ctx.Command.PluginType is not null - ? (Plugin)ctx.Services.GetRequiredService(ctx.Command.PluginType) - : null; - var logger = plugin is not null - ? (ILogger)Services.GetRequiredService(plugin.LoggerType) - : Logger; - - var invocationCancelled = false; - if (CommandInvoking is not null) - { - var invokingEvent = new CommandInvokingEvent(ctx.Command, ctx.Message); - CommandInvoking.Invoke(this, invokingEvent); - invocationCancelled = invokingEvent.IsCancelled; - } - - if (invocationCancelled) - return null; - - var cmdCtx = new CommandContext(ctx.App, ctx.Bot, ctx.Message); - MessageContent? content = null; - try - { - content = await ctx.Command.InvokeAsync(plugin, cmdCtx, parseResult, logger); - } - catch (Exception e) - { - ctx.Exception = e.InnerException ?? e; - } - - CommandInvoked?.Invoke(this, new CommandInvokedEvent(ctx.Command, ctx.Message, ctx.Exception, content)); - return content; - } - } -} diff --git a/src/Flandre.Framework/PluginCollection.cs b/src/Flandre.Framework/PluginCollection.cs deleted file mode 100644 index efb01cb..0000000 --- a/src/Flandre.Framework/PluginCollection.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Flandre.Framework.Common; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Flandre.Framework; - -/// -/// 插件集合 -/// -public interface IPluginCollection -{ - /// - /// 插件使用的服务 - /// - IServiceCollection Services { get; } - - /// - /// 配置 - /// - IConfiguration Configuration { get; } - - /// - /// 添加插件 - /// - /// 插件类型 - IPluginCollection Add(Type pluginType); -} - -internal sealed class PluginCollection : IPluginCollection -{ - public PluginCollection(IServiceCollection services, IConfiguration configuration) - { - Services = services; - Configuration = configuration; - } - - public List PluginTypes { get; } = new(); - - public IServiceCollection Services { get; } - - public IConfiguration Configuration { get; } - - public IPluginCollection Add(Type pluginType) - { - PluginTypes.Add(pluginType); - Services.AddScoped(pluginType); - return this; - } -} - -/// -/// 插件集合扩展方法 -/// -public static class PluginCollectionExtensions -{ - /// - /// 添加插件 - /// - /// - /// - /// - public static IPluginCollection Add(this IPluginCollection plugins) where TPlugin : Plugin - { - return plugins.Add(typeof(TPlugin)); - } - - /// - /// 添加一个带配置的插件 - /// - /// - /// - /// - /// - /// - public static IPluginCollection Add(this IPluginCollection plugins, - IConfiguration configuration) - where TPlugin : Plugin where TPluginOptions : class - { - plugins.Add(); - plugins.Services.Configure(configuration); - return plugins; - } - - /// - /// 添加一个带配置的插件 - /// - /// - /// - /// - /// - /// - public static IPluginCollection Add(this IPluginCollection plugins, - Action action) - where TPlugin : Plugin where TPluginOptions : class - { - plugins.Add(); - plugins.Services.Configure(action); - return plugins; - } -} diff --git a/src/Flandre.Framework/Routing/CommandAttribute.cs b/src/Flandre.Framework/Routing/CommandAttribute.cs deleted file mode 100644 index cf24abe..0000000 --- a/src/Flandre.Framework/Routing/CommandAttribute.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Flandre.Core.Models; - -namespace Flandre.Framework.Routing; - -/// -/// 指令 -/// -/// 被这条特性指定的方法,会自动作为指令被机器人加载 -[AttributeUsage(AttributeTargets.Method)] -public class CommandAttribute : Attribute -{ - /// - /// 构造特性实例 - /// - /// - /// - public CommandAttribute(string fullName, params string[] aliases) - { - FullName = fullName; - Aliases = aliases; - } - - /// - /// 构造特性实例 - /// - public CommandAttribute() - { - FullName = null; - Aliases = Array.Empty(); - } - - /// - /// 指令的全名 - /// - public string? FullName { get; } - - /// - /// 指令别名(全名) - /// - public string[] Aliases { get; } - - /// - /// 能触发该指令的用户身份 - /// - /// 默认值为 - public UserRole Role { get; init; } = UserRole.Member; -} diff --git a/src/Flandre.Framework/Routing/CommandNode.cs b/src/Flandre.Framework/Routing/CommandNode.cs deleted file mode 100644 index fffafc8..0000000 --- a/src/Flandre.Framework/Routing/CommandNode.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Flandre.Framework.Common; - -namespace Flandre.Framework.Routing; - -/// -/// 指令节点 -/// -public sealed class CommandNode -{ - /// - /// 以 . 分割的指令完整路径 - /// - public string FullName { get; } - - /// - /// 指令对象,如果该节点不包含指令则为 null - /// - public Command? Command { get; internal set; } - - /// - /// 子节点 - /// - public Dictionary SubNodes { get; } = new(); - - /// - /// 当前节点包含指令 - /// - public bool HasCommand => Command is not null; - - /// - /// 当前指令节点为某个指令的别名 - /// - public bool IsAlias { get; internal set; } - - internal CommandNode(string fullName) => FullName = fullName; - - internal Command MapCommand(Type? pluginType, string relativePath) - { - var segments = relativePath.Split('.', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); - var currentNode = this; - - for (var i = 0; i < segments.Length; i++) - { - currentNode = currentNode.SubNodes.TryGetValue(segments[i], out var nextNode) - ? nextNode - : currentNode.SubNodes[segments[i]] = new CommandNode( - string.Join('.', segments[..(i + 1)])); - } - - var finalName = segments[^1]; - var command = new Command(currentNode, pluginType, finalName, currentNode.FullName); - currentNode.Command = command; - return command; - } - - /// - /// 移除本身节点所含指令,并清除所有子节点 - /// - public void Clear() - { - Command = null; - IsAlias = false; - SubNodes.Clear(); - } -} diff --git a/src/Flandre.Framework/Routing/CommandNodeExtensions.cs b/src/Flandre.Framework/Routing/CommandNodeExtensions.cs deleted file mode 100644 index 4d56b31..0000000 --- a/src/Flandre.Framework/Routing/CommandNodeExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Flandre.Framework.Routing; - -/// -/// -/// -public static class CommandNodeExtensions -{ - /// - /// 寻找子节点 - /// - /// - /// - /// - public static CommandNode? FindSubNode(this CommandNode node, string relativePath) - { - foreach (var name in relativePath.Split('.', - StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)) - if (node.SubNodes.Keys.FirstOrDefault(k => k.Equals(name, StringComparison.OrdinalIgnoreCase)) is { } key) - node = node.SubNodes[key]; - else - return null; - - return node; - } - - public static int CountCommands(this CommandNode node) - { - var count = 0; - - void CountNodeCommands(CommandNode nowNode) - { - if (nowNode is { HasCommand: true, IsAlias: false }) - count++; - foreach (var (_, subNode) in nowNode.SubNodes) - CountNodeCommands(subNode); - } - - CountNodeCommands(node); - return count; - } -} diff --git a/src/Flandre.Framework/Routing/CommandRouteBuilderExtensions.cs b/src/Flandre.Framework/Routing/CommandRouteBuilderExtensions.cs deleted file mode 100644 index 84302e3..0000000 --- a/src/Flandre.Framework/Routing/CommandRouteBuilderExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Flandre.Framework.Services; -using Microsoft.Extensions.DependencyInjection; - -namespace Flandre.Framework.Routing; - -/// -/// 指令路由构造器的扩展方法 -/// -public static class CommandRouteBuilderExtensions -{ - /// - /// 添加指令 - /// - /// - /// - /// - public static void MapCommand(this ICommandRouteBuilder builder, string fullname, Delegate commandDelegate) - { - var cmdService = builder.Services.GetRequiredService(); - cmdService.RootNode.MapCommand(null, fullname) - .WithAction(commandDelegate); - } -} diff --git a/src/Flandre.Framework/Routing/ICommandRouteBuilder.cs b/src/Flandre.Framework/Routing/ICommandRouteBuilder.cs deleted file mode 100644 index 826063f..0000000 --- a/src/Flandre.Framework/Routing/ICommandRouteBuilder.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Flandre.Framework.Routing; - -/// -/// 指令路由构造器 -/// -public interface ICommandRouteBuilder -{ - /// - /// 服务提供源 - /// - IServiceProvider Services { get; } -} diff --git a/src/Flandre.Framework/Services/CommandService.cs b/src/Flandre.Framework/Services/CommandService.cs deleted file mode 100644 index c6feb8b..0000000 --- a/src/Flandre.Framework/Services/CommandService.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Flandre.Core.Utils; -using Flandre.Framework.Common; -using Flandre.Framework.Routing; - -namespace Flandre.Framework.Services; - -internal sealed class CommandService -{ - public CommandNode RootNode { get; } = new(""); - - public Dictionary StringShortcuts { get; } = new(); - - public Dictionary RegexShortcuts { get; } = new(); - - internal Dictionary TypeFriendlyNames { get; } = new(); - - internal Dictionary TypeResolvers { get; } = new(); - - internal CommandService() - { - AddInternalTypeResolvers(); - } - - public void MapTypeResolver(string? typeFriendlyName, TypeResolverDelegate resolver) - { - var type = typeof(T); - TypeResolvers[type] = (string raw, out object? result) => - { - var suc = resolver(raw, out var res); - result = res; - return suc; - }; - - if (typeFriendlyName is not null) - TypeFriendlyNames[type] = typeFriendlyName; - } - - public bool TryParseArgumentValue(Type type, string raw, out object? result) - { - if (TypeResolvers.TryGetValue(type, out var typeResolver)) - return typeResolver(raw, out result); - - result = null; - return false; - } - - public string GetTypeFriendlyName(Type type) - { - return TypeFriendlyNames.TryGetValue(type, out var name) - ? name - : type.Name; - } - - internal void Reset() - { - RootNode.Clear(); - StringShortcuts.Clear(); - RegexShortcuts.Clear(); - TypeFriendlyNames.Clear(); - TypeResolvers.Clear(); - AddInternalTypeResolvers(); - } - - #region 初始化 - - private void AddInternalTypeResolvers() - { - MapTypeResolver("整数", int.TryParse); - MapTypeResolver("整数", long.TryParse); - MapTypeResolver("整数", byte.TryParse); - MapTypeResolver("整数", sbyte.TryParse); - MapTypeResolver("整数", short.TryParse); - - MapTypeResolver("正整数", uint.TryParse); - MapTypeResolver("正整数", ulong.TryParse); - MapTypeResolver("正整数", ushort.TryParse); - - MapTypeResolver("小数", double.TryParse); - MapTypeResolver("小数", float.TryParse); - MapTypeResolver("小数", decimal.TryParse); - - MapTypeResolver("\"true\"或\"false\"", bool.TryParse); - - MapTypeResolver("字符", char.TryParse); - - // ReSharper disable once RedundantTypeArgumentsOfMethod - MapTypeResolver("不带空格的文本", (string raw, out string result) => - { - result = new StringParser(raw).ReadQuoted(); - return true; - }); - } - - #endregion -} diff --git a/src/Flandre.Framework/Services/MiddlewareService.cs b/src/Flandre.Framework/Services/MiddlewareService.cs deleted file mode 100644 index 87fefc4..0000000 --- a/src/Flandre.Framework/Services/MiddlewareService.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Flandre.Framework.Common; - -namespace Flandre.Framework.Services; - -/// -/// 中间件服务 -/// -public sealed class MiddlewareService -{ - private readonly List, Task>> _middleware = new(); -} diff --git a/src/Flandre.Framework/Utils/LogUtils.cs b/src/Flandre.Framework/Utils/LogUtils.cs deleted file mode 100644 index baa3697..0000000 --- a/src/Flandre.Framework/Utils/LogUtils.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace Flandre.Framework.Utils; - -internal static class LogUtils -{ - internal static ILogger GetTempLogger() - { - using var factory = new LoggerFactory(); - return factory.CreateLogger(); - } -} diff --git a/src/Flandre.Framework/Utils/MessageUtils.cs b/src/Flandre.Framework/Utils/MessageUtils.cs deleted file mode 100644 index 9015e4a..0000000 --- a/src/Flandre.Framework/Utils/MessageUtils.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Flandre.Core.Messaging; - -namespace Flandre.Framework.Utils; - -internal static class MessageUtils -{ - internal static string GetUserMark(this MessageContext ctx) - { - return $"{ctx.Platform}:{ctx.GuildId}:{ctx.UserId}"; - } -} diff --git a/tests/Flandre.Core.Reactive.Tests/CoreReactiveExtensionsTests.cs b/tests/Flandre.Core.Reactive.Tests/CoreReactiveExtensionsTests.cs deleted file mode 100644 index 810bce7..0000000 --- a/tests/Flandre.Core.Reactive.Tests/CoreReactiveExtensionsTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Reactive.Linq; -using Flandre.Adapters.Mock; - -namespace Flandre.Core.Reactive.Tests; - -public class CoreReactiveExtensionsTests -{ - [Fact] - public void TestObserveMessageReceived() - { - var adapter = new MockAdapter(); - var client1 = adapter.GetChannelClient("testG1", "testC1", "123"); - var client2 = adapter.GetChannelClient("testG2", "testC2", "456"); - var bot = adapter.Bots.First(); - - var messageCountFrom1 = 0; - var messageCountFrom2 = 0; - var allMessageReceived = 0; - - bot.OnMessageReceived() - .OfUser("123") - .Subscribe(_ => messageCountFrom1++); - - bot.OnMessageReceived() - .Select(e => e.Message) - .OfGuild("testG1") - .Subscribe(_ => messageCountFrom1++); - - bot.OnMessageReceived() - .Select(e => e.Message) - .OfUser("456") - .Subscribe(_ => messageCountFrom2++); - - bot.OnMessageReceived() - .OfChannel("testC2") - .Subscribe(_ => messageCountFrom2++); - - bot.OnMessageReceived() - .Subscribe(_ => allMessageReceived++); - - - client1.SendMessage("abc"); - client1.SendMessage("abc"); - client2.SendMessage("abc"); - - Assert.Equal(4, messageCountFrom1); - Assert.Equal(2, messageCountFrom2); - Assert.Equal(3, allMessageReceived); - } -} diff --git a/tests/Flandre.Core.Reactive.Tests/Flandre.Core.Reactive.Tests.csproj b/tests/Flandre.Core.Reactive.Tests/Flandre.Core.Reactive.Tests.csproj deleted file mode 100644 index fe5c7d7..0000000 --- a/tests/Flandre.Core.Reactive.Tests/Flandre.Core.Reactive.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net6.0 - enable - enable - - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - diff --git a/tests/Flandre.Core.Reactive.Tests/Usings.cs b/tests/Flandre.Core.Reactive.Tests/Usings.cs deleted file mode 100644 index c802f44..0000000 --- a/tests/Flandre.Core.Reactive.Tests/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Xunit; diff --git a/tests/Flandre.Core.Tests/Flandre.Core.Tests.csproj b/tests/Flandre.Core.Tests/Flandre.Core.Tests.csproj deleted file mode 100644 index 0e9dfd3..0000000 --- a/tests/Flandre.Core.Tests/Flandre.Core.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net6.0 - enable - enable - - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - diff --git a/tests/Flandre.Core.Tests/Usings.cs b/tests/Flandre.Core.Tests/Usings.cs deleted file mode 100644 index c802f44..0000000 --- a/tests/Flandre.Core.Tests/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Xunit; diff --git a/tests/Flandre.Core.Tests/UtilsTests/StringParserTests.cs b/tests/Flandre.Core.Tests/UtilsTests/StringParserTests.cs deleted file mode 100644 index 815a583..0000000 --- a/tests/Flandre.Core.Tests/UtilsTests/StringParserTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Flandre.Core.Utils; - -namespace Flandre.Core.Tests.UtilsTests; - -public class StringParserTests -{ - [Fact] - public void TestStringParser() - { - const string str = "Str ing Parser_[Tests]"; - var parser = new StringParser(str); - - Assert.Equal('S', parser.Current); - - parser.Skip(1); - Assert.Equal('t', parser.Current); - - parser.Skip('r'); - Assert.Equal('r', parser.Current); - - parser.Skip(1).SkipWhiteSpaces(); // skip 'r' and skip spaces - Assert.Equal('i', parser.Current); - - Assert.Equal("ing", parser.Peek(3)); - - Assert.Equal("ing P", parser.Peek('a')); - - Assert.Equal("ing", parser.Read(' ')); - Assert.Equal(' ', parser.Current); - - Assert.Equal("Parser_[Tests]", parser.SkipWhiteSpaces().ReadToEnd()); - } - - [Fact] - public void TestQuotes() - { - const string str = "\"alpha\" 'beta' gamma other"; - var parser = new StringParser(str); - - Assert.Equal("alpha", parser.ReadQuoted()); - parser.SkipWhiteSpaces(); - - Assert.Equal("beta", parser.ReadQuoted()); - parser.SkipWhiteSpaces(); - - Assert.Equal("gamma", parser.ReadQuoted()); - - // 'x' is not in the rest of the string, so default to ReadToEnd. - Assert.Equal(" other", parser.Read('x')); - } -} diff --git a/tests/Flandre.Core.Tests/UtilsTests/TextUtilsTests.cs b/tests/Flandre.Core.Tests/UtilsTests/TextUtilsTests.cs deleted file mode 100644 index 86dc267..0000000 --- a/tests/Flandre.Core.Tests/UtilsTests/TextUtilsTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Flandre.Core.Utils; - -namespace Flandre.Core.Tests.UtilsTests; - -public class TextUtilsTests -{ - [Theory] - [InlineData(" Alp ha Bet a ", "AlphaBeta", " ")] - [InlineData("-a --beta", "-a beta", "--")] - public void TestRemoveString(string source, string result, string removal) - { - Assert.Equal(result, source.RemoveString(removal)); - } - - [Theory] - [InlineData("string", "ring", "st")] - [InlineData("string", "string", "")] - public void TestTrimStart(string source, string result, string trim) - { - Assert.Equal(result, source.TrimStart(trim)); - } -} diff --git a/tests/Flandre.Framework.Tests/AppEventsTests.cs b/tests/Flandre.Framework.Tests/AppEventsTests.cs deleted file mode 100644 index a468e22..0000000 --- a/tests/Flandre.Framework.Tests/AppEventsTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Flandre.Framework.Routing; - -namespace Flandre.Framework.Tests; - -public class AppEventsTests -{ - private class TestPlugin : Plugin - { - [Command("throw-ex")] - public static MessageContent OnThrowEx(CommandContext ctx) => - throw new Exception("Test Exception"); - } - - [Fact] - public async Task TestEvents() - { - await using var app = Utils.CreateTestApp(out var client); - - var count = 0; - string? cmdName = null; - Exception? ex = null; - - app.Starting += (_, _) => count += 1; - app.Ready += (_, _) => - { - count += 10; - client.SendMessage("throw-ex"); - }; - app.Stopped += (_, _) => count += 100; - - app.CommandInvoking += (_, e) => cmdName = e.Command.Name; - app.CommandInvoked += (_, e) => { ex = e.Exception; }; - - await app.StartWithDefaultsAsync(); - await Task.Delay(TimeSpan.FromSeconds(1)); - await app.StopAsync(); - - Assert.Equal(111, count); - Assert.Equal("throw-ex", cmdName); - Assert.Equal("Test Exception", ex?.Message); - } -} diff --git a/tests/Flandre.Framework.Tests/CommandNodeTests.cs b/tests/Flandre.Framework.Tests/CommandNodeTests.cs deleted file mode 100644 index 48655e8..0000000 --- a/tests/Flandre.Framework.Tests/CommandNodeTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Flandre.Framework.Routing; -using Flandre.Framework.Services; -using Microsoft.Extensions.DependencyInjection; - -namespace Flandre.Framework.Tests; - -public class CommandNodeTests -{ - private class TestPlugin : Plugin - { - [Command("cmd-xxx")] - public static MessageContent? ACommandInRoot() => null; - - [Command("...cmd-aaa.cmd-bbb...")] - public static MessageContent? ASubCommand() => null; - - [Command(".cmd-bbb")] - public static MessageContent? AnotherCommandInRoot() => null; - } - - [Fact] - public void TestNode() - { - using var app = Utils.StartTestApp(out _); - - var cmdService = app.Services.GetRequiredService(); - - Assert.Equal(3, cmdService.RootNode.CountCommands()); - Assert.Equal("cmd-xxx", cmdService.RootNode.FindSubNode("cmd-xxx")?.FullName); - Assert.Equal("cmd-aaa.cmd-bbb", cmdService.RootNode.FindSubNode("cmd-aaa..cmd-bbb")?.FullName); - Assert.Equal("cmd-aaa.cmd-bbb", cmdService.RootNode.FindSubNode("cmd-aaa..cmd-bbb")?.Command?.FullName); - Assert.Equal("cmd-bbb", cmdService.RootNode.FindSubNode("cmd-bbb . .")?.Command?.FullName); - } -} diff --git a/tests/Flandre.Framework.Tests/CommandTests.cs b/tests/Flandre.Framework.Tests/CommandTests.cs deleted file mode 100644 index fa7ac91..0000000 --- a/tests/Flandre.Framework.Tests/CommandTests.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System.ComponentModel; -using Flandre.Core.Common; -using Flandre.Framework.Routing; -using Flandre.Framework.Services; -using Microsoft.Extensions.DependencyInjection; - -// ReSharper disable UnusedMember.Local - -namespace Flandre.Framework.Tests; - -public class CommandTests -{ - private sealed class TestPlugin : Plugin - { - public override async Task OnMessageReceivedAsync(MessageContext ctx) - { - if (ctx.Message.GetText().StartsWith("OMR:")) - await ctx.Bot.SendMessageAsync(ctx.Message); - } - - [Command] - [Description("This is a test command.")] - public static MessageContent Test1(bool arg1, [Option] double opt = 0) - { - return new MessageBuilder() - .Text($"{arg1} {opt + 200}") - .Build(); - } - - [Command("test2", "..test111.11...45.14.")] - [Obsolete("This command is obsoleted.")] - public static string Test2(int arg1, float arg2, CommandContext ctx, - [Option] bool opt1 = true, [Option(ShortName = 'o')] bool opt2 = false) - { - return $"{arg1} {arg2} {opt1} {opt2}"; - } - - [Command] - [RegexShortcut("测([0-9A-Za-z_])试", "$1 someStr")] - public static string Test3(string arg1, string arg2) - { - return $"{arg1} {arg2}"; - } - - [Command] - [StringShortcut("测试4", "123.456")] - public static string Test4(double arg1) - { - return $"{arg1}"; - } - - [Command] - [StringShortcut("测试5", "111.222 --opt1", AllowArguments = true)] - public static string Test5(double arg1, int arg2, [Option] bool opt1) - { - return $"{arg1} {arg2} {opt1}"; - } - - // Array parameter - [Command] - public string Test6(double arg, params string[] strArr) - { - return $"{arg} | {string.Join(',', strArr)} | {strArr.Length}"; - } - - [Command] - public static async ValueTask TestAsync() - { - // simulates async tasks - await Task.Run(() => { }); - return "ok!"; - } - } - - [Fact] - public void TestCommands() - { - using var app = Utils.StartTestApp(out var client); - - var service = app.Services.GetRequiredService(); - - Assert.Equal(7, service.RootNode.CountCommands()); - - MessageContent? content; - - content = client.SendMessageForReply("OMR:114514"); - Assert.Equal("OMR:114514", content?.GetText()); - - content = client.SendMessageForReply("test1 --opt 114.514 true"); - Assert.Equal("True 314.514", content?.GetText()); - // - content = client.SendMessageForReply("test2 -o 123 191.981 --no-opt1"); - Assert.Equal("123 191.981 False True", - content?.GetText()); - - // test async - content = client.SendMessageForReply("testasync"); - Assert.Equal("ok!", content?.GetText()); - } - - [Fact] - public void TestShortcuts() - { - using var app = Utils.StartTestApp(out var client); - - MessageContent? content; - - content = client.SendMessageForReply("测3试"); - Assert.Equal("3 someStr", content?.GetText()); - - content = client.SendMessageForReply("测试4"); - Assert.Equal("123.456", content?.GetText()); - - content = client.SendMessageForReply("测试4 114.514", TimeSpan.FromSeconds(2)); - Assert.Null(content?.GetText()); - - content = client.SendMessageForReply("测试5 333"); - Assert.Equal("111.222 333 True", content?.GetText()); - } - - [Fact] - public void TestMapCommand() - { - using var app = Utils.StartTestApp(out var client); - - app.MapCommand("test1", (int a, int b) => a + b); - app.MapCommand("test2.sub", (int x) => Math.Pow(x, 2)); - - var content = client.SendMessageForReply("test1 123 456"); - Assert.Equal("579", content?.GetText()); - - content = client.SendMessageForReply("test2 sub 12"); - Assert.Equal("144", content?.GetText()); - } - - [Fact] - public void TestArrayParameter() - { - using var app = Utils.StartTestApp(out var client); - - var content = client.SendMessageForReply("test6 1.23 aaa bbb ccc "); - Assert.Equal("1.23 | aaa,bbb,ccc | 3", content?.GetText()); - - content = client.SendMessageForReply("test6 2.34"); - Assert.Equal("2.34 | | 0", content?.GetText()); - } - - [Fact] - public void TestInformalAttributes() - { - using var app = Utils.StartTestApp(out _); - - var cmdService = app.Services.GetRequiredService(); - - Assert.Equal("This is a test command.", - cmdService.RootNode.FindSubNode("test1")?.Command?.Description); - - Assert.True(cmdService.RootNode.FindSubNode("test2")?.Command?.IsObsolete); - Assert.Equal("This command is obsoleted.", - cmdService.RootNode.FindSubNode("test2")?.Command?.ObsoleteMessage); - } -} diff --git a/tests/Flandre.Framework.Tests/Flandre.Framework.Tests.csproj b/tests/Flandre.Framework.Tests/Flandre.Framework.Tests.csproj deleted file mode 100644 index bce8cd5..0000000 --- a/tests/Flandre.Framework.Tests/Flandre.Framework.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net6.0 - enable - enable - - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - diff --git a/tests/Flandre.Framework.Tests/FlandreAppTests.cs b/tests/Flandre.Framework.Tests/FlandreAppTests.cs deleted file mode 100644 index 12ccf26..0000000 --- a/tests/Flandre.Framework.Tests/FlandreAppTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -// ReSharper disable StringLiteralTypo - -using Flandre.Framework.Routing; -using Flandre.Framework.Services; -using Microsoft.Extensions.DependencyInjection; - -namespace Flandre.Framework.Tests; - -public class FlandreAppTests -{ - private class TestPlugin : Plugin - { - [Command("test", " .. test111..11.45..14 . ")] - [StringShortcut("测试")] - public static MessageContent OnTest(CommandContext ctx, bool arg1, [Option] double opt = 0) - { - return $"{arg1} {opt + 200}"; - } - - [Command("sub.test", "sssuuubbb")] - [StringShortcut("子测试")] - public static MessageContent? OnSubTest(CommandContext ctx) => null; - - [Command("...sub....sub..sub......test..")] - // lang=regex - [RegexShortcut(@"\d\d\d", "$1")] - public static MessageContent? OnSubSubSubTest(CommandContext ctx) => null; - } - - [Fact] - public void TestAliases() - { - using var app = Utils.StartTestApp(out _); - - var cmdService = app.Services.GetRequiredService(); - - Assert.Equal(3, cmdService.RootNode.CountCommands()); - - Assert.NotNull(cmdService.RootNode.FindSubNode("test")); - Assert.NotNull(cmdService.RootNode.FindSubNode("sub.test")); - Assert.NotNull(cmdService.RootNode.FindSubNode("sub.sub.sub.test")); - - // alias - Assert.NotNull(cmdService.RootNode.FindSubNode("test111.11.45.14")); - Assert.Equal(cmdService.RootNode.FindSubNode("sssuuubbb")?.Command, - cmdService.RootNode.FindSubNode("sub.test")?.Command); - } - - [Fact] - public void TestShortcutCount() - { - using var app = Utils.StartTestApp(out _); - - var cmdService = app.Services.GetRequiredService(); - - Assert.Equal(2, cmdService.StringShortcuts.Count); - Assert.Single(cmdService.RegexShortcuts); - } -} diff --git a/tests/Flandre.Framework.Tests/MiddlewareTests.cs b/tests/Flandre.Framework.Tests/MiddlewareTests.cs deleted file mode 100644 index 5a1abee..0000000 --- a/tests/Flandre.Framework.Tests/MiddlewareTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace Flandre.Framework.Tests; - -public class MiddlewareTests -{ - [Fact] - public async Task TestMiddleware() - { - await using var app = Utils.CreateTestApp(out var client); - - // Order Note - // 1,2,3 ↓ ↑ 2 - // [Custom Middleware #1] - // 2 ↓ ↑ 2 - // [Custom Middleware #2] - - int count1In = 0, count1Out = 0, count2 = 0; - - // Custom Middleware #1 - app.Use(async (ctx, next) => - { - // 1, 2, 3 pass through - - if (ctx.Message.GetText().Contains("(3)")) - // 3 shorts here - return; - - count1In++; - - if (ctx.Message.GetText().Contains("(1)")) - { - ctx.Response = "ok"; - // 1 shorts here - return; - } - - // only 2 passes through the next middleware - await next(); - // 2 goes out - - count1Out++; - }); - - // Custom Middleware #2 - app.Use(async (ctx, next) => - { - // 2 passes through - count2++; - - ctx.Response = ctx.Message.Content; - - // 2 goes out - await next(); - }); - - await app.StartAsync(); - - var content1 = client.SendMessageForReply("test (1) short me at middleware #1"); - Assert.NotNull(content1); - - var content2 = client.SendMessageForReply("test (2) pass me through all middleware"); - Assert.NotNull(content2); - - var content3 = client.SendMessageForReply("test (3) don't pass me", TimeSpan.FromSeconds(2)); - Assert.Null(content3); - - Assert.Equal(2, count1In); - Assert.Equal(1, count2); - Assert.Equal(1, count1Out); - } -} diff --git a/tests/Flandre.Framework.Tests/SessionTests.cs b/tests/Flandre.Framework.Tests/SessionTests.cs deleted file mode 100644 index b44d2b3..0000000 --- a/tests/Flandre.Framework.Tests/SessionTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Flandre.Framework.Routing; - -namespace Flandre.Framework.Tests; - -public class SessionTests -{ - private sealed class TestPlugin : Plugin - { - [Command("start-session")] - public static async Task OnStartSession(CommandContext ctx) - { - var nextMsg = await ctx.StartSessionAsync(TimeSpan.FromSeconds(2)); - return nextMsg?.Content; - } - } - - [Fact] - public async Task TestCommandSession() - { - await using var app = Utils.StartTestApp(out var client); - - var task1 = client.SendMessageForReplyAsync("start-session"); - await Task.Delay(TimeSpan.FromSeconds(1)); - client.SendMessage("return this"); - Assert.NotNull(await task1); - - var task2 = client.SendMessageForReplyAsync("start-session"); - await Task.Delay(TimeSpan.FromSeconds(3)); - client.SendMessage("timeout!"); - Assert.Null(await task2); - } -} diff --git a/tests/Flandre.Framework.Tests/Usings.cs b/tests/Flandre.Framework.Tests/Usings.cs deleted file mode 100644 index bda8fb5..0000000 --- a/tests/Flandre.Framework.Tests/Usings.cs +++ /dev/null @@ -1,4 +0,0 @@ -global using Xunit; -global using Flandre.Adapters.Mock; -global using Flandre.Framework.Common; -global using Flandre.Core.Messaging; diff --git a/tests/Flandre.Framework.Tests/Utils.cs b/tests/Flandre.Framework.Tests/Utils.cs deleted file mode 100644 index 9ba5849..0000000 --- a/tests/Flandre.Framework.Tests/Utils.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace Flandre.Framework.Tests; - -public static class Utils -{ - public static FlandreAppBuilder CreateTestBuilder() where TPlugin : Plugin - { - var builder = FlandreApp.CreateBuilder(); - builder.Plugins.Add(); - return builder; - } - - public static FlandreApp CreateTestApp(out MockClient client, bool useFriendClient = false) - where TPlugin : Plugin - { - var builder = CreateTestBuilder(); - - var adapter = new MockAdapter(); - client = useFriendClient ? adapter.GetFriendClient() : adapter.GetChannelClient(); - builder.Adapters.Add(adapter); - - return builder.Build(); - } - - public static FlandreApp CreateTestApp(out MockClient client, bool useFriendClient = false) - { - var builder = FlandreApp.CreateBuilder(); - - var adapter = new MockAdapter(); - client = useFriendClient ? adapter.GetFriendClient() : adapter.GetChannelClient(); - builder.Adapters.Add(adapter); - - return builder.Build(); - } - - public static FlandreApp StartTestApp(out MockClient client, bool useFriendClient = false) - where TPlugin : Plugin - { - var app = CreateTestApp(out client, useFriendClient); - app.StartWithDefaultsAsync().Wait(); - return app; - } - - public static FlandreApp StartTestApp(out MockClient client, bool useFriendClient = false) - { - var app = CreateTestApp(out client, useFriendClient); - app.StartWithDefaultsAsync().Wait(); - return app; - } -}