diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..5031f72e7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,243 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = lf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +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 + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = false +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = false +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +# lwx like var +csharp_style_var_elsewhere = true +# but don't like built in types use var +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = true + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +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 + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +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 + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +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 + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +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.types.required_modifiers = + +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.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +[*.{cs,vb}] +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = lf \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index e6e9b9f95..ed4d3ad9e 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -139,7 +139,7 @@ body: - type: textarea id: logging attributes: - label: 日志记录(可选) + label: Trace 级别日志记录(可选) render: Shell # Optional | Extra description diff --git a/.gitignore b/.gitignore index c6f8d3863..9a7ed67fe 100644 --- a/.gitignore +++ b/.gitignore @@ -269,6 +269,3 @@ docs/node_modules docs/.vitepress/dist docs/.vitepress/cache docs/api - -# code style -.editorconfig \ No newline at end of file diff --git a/Docker.md b/Docker.md index ec3cccd4f..2b2f6a0fb 100644 --- a/Docker.md +++ b/Docker.md @@ -22,7 +22,7 @@ An Implementation of NTQQ Protocol, with Pure C#, Derived from Konata.Core # 8081 port for ForwardWebSocket and Http-Post # /path-to-data is used to store the files needed for the runtime # UID Env and GID Env are used to set file permissions -docker run -td -p 8081:8081 -v /path-to-data:/app/data -e UID=$UID -e GID=$(id -g) ghcr.io/konatadev/lagrange.onebot:edge +docker run -td -p 8081:8081 -v /path-to-data:/app/data -e UID=$UID -e GID=$(id -g) ghcr.io/lagrangedev/lagrange.onebot:edge ``` > [!IMPORTANT] diff --git a/Docker_zh.md b/Docker_zh.md index 02230dc86..269eb28ca 100644 --- a/Docker_zh.md +++ b/Docker_zh.md @@ -22,7 +22,7 @@ # 8081 端口用于正向 WebSocket 和 Http-post # /path-to-data 被用于存储程序运行时产生的文件 # UID Env 和 GID Env 用于设置文件权限 -docker run -td -p 8081:8081 -v /path-to-data:/app/data -e UID=$UID -e GID=$(id -g) ghcr.io/konatadev/lagrange.onebot:edge +docker run -td -p 8081:8081 -v /path-to-data:/app/data -e UID=$UID -e GID=$(id -g) ghcr.io/lagrangedev/lagrange.onebot:edge ``` > [!IMPORTANT] diff --git a/Lagrange.Core.Test/Tests/EcdhTest.cs b/Lagrange.Core.Test/Tests/EcdhTest.cs new file mode 100644 index 000000000..29878da77 --- /dev/null +++ b/Lagrange.Core.Test/Tests/EcdhTest.cs @@ -0,0 +1,82 @@ +using Lagrange.Core.Utility.Crypto.Provider.Ecdh; + +namespace Lagrange.Core.Test.Tests; + +public class EcdhTest +{ + public static void Test() + { + { + var uncompressedPublicKey = new byte[] + { + 0x04, + 0xEB, 0xCA, 0x94, 0xD7, 0x33, 0xE3, 0x99, 0xB2, + 0xDB, 0x96, 0xEA, 0xCD, 0xD3, 0xF6, 0x9A, 0x8B, + 0xB0, 0xF7, 0x42, 0x24, 0xE2, 0xB4, 0x4E, 0x33, + 0x57, 0x81, 0x22, 0x11, 0xD2, 0xE6, 0x2E, 0xFB, + 0xC9, 0x1B, 0xB5, 0x53, 0x09, 0x8E, 0x25, 0xE3, + 0x3A, 0x79, 0x9A, 0xDC, 0x7F, 0x76, 0xFE, 0xB2, + 0x08, 0xDA, 0x7C, 0x65, 0x22, 0xCD, 0xB0, 0x71, + 0x9A, 0x30, 0x51, 0x80, 0xCC, 0x54, 0xA8, 0x2E + }; + var compressedPublicKey = new byte[] + { + 0x02, + 0xEB, 0xCA, 0x94, 0xD7, 0x33, 0xE3, 0x99, 0xB2, + 0xDB, 0x96, 0xEA, 0xCD, 0xD3, 0xF6, 0x9A, 0x8B, + 0xB0, 0xF7, 0x42, 0x24, 0xE2, 0xB4, 0x4E, 0x33, + 0x57, 0x81, 0x22, 0x11, 0xD2, 0xE6, 0x2E, 0xFB + }; + + EcdhProvider ecdhProvider = new EcdhProvider(EllipticCurve.Prime256V1); + + var unpacked1 = ecdhProvider.UnpackPublic(uncompressedPublicKey); + var unpacked2 = ecdhProvider.UnpackPublic(compressedPublicKey); + + if (unpacked1.X == unpacked2.X && unpacked1.Y == unpacked2.Y) + { + Console.WriteLine("UnpackPublic() works correctly"); + } + else + { + Console.WriteLine("UnpackPublic() does not work correctly"); + } + } + { + // from https://github.com/KonataDev/Konata.Core/blob/develop/Konata.Core.Test/UtilTest/EcdhTest.cs + var uncompressedPublicKey = new byte[] + { + 0x04, + 0xD5, 0xCF, 0xB0, 0x2D, 0x5D, 0x4F, 0xCA, 0x2C, + 0x84, 0xF6, 0xF1, 0x29, 0x4B, 0x45, 0x5B, 0xAB, + 0x4C, 0x96, 0x98, 0xDD, 0x57, 0x2B, 0xF8, 0x63, + 0x82, 0xA9, 0xDA, 0xF8, 0xAD, 0xE9, 0xD4, 0x5A, + 0x57, 0xDE, 0x14, 0x04, 0xFA, 0x5D, 0x41, 0x29, + 0x1E, 0x0A, 0x56, 0xCB, 0x45, 0x08, 0xD3, 0x2F + }; + var compressedPublicKey = new byte[] + { + 0x03, + 0xD5, 0xCF, 0xB0, 0x2D, 0x5D, 0x4F, 0xCA, 0x2C, + 0x84, 0xF6, 0xF1, 0x29, 0x4B, 0x45, 0x5B, 0xAB, + 0x4C, 0x96, 0x98, 0xDD, 0x57, 0x2B, 0xF8, 0x63 + }; + + EcdhProvider ecdhProvider = new EcdhProvider(EllipticCurve.Secp192K1); + + var unpacked1 = ecdhProvider.UnpackPublic(uncompressedPublicKey); + var unpacked2 = ecdhProvider.UnpackPublic(compressedPublicKey); + + if (unpacked1.X == unpacked2.X && unpacked1.Y == unpacked2.Y) + { + Console.WriteLine("UnpackPublic() works correctly"); + } + else + { + Console.WriteLine("UnpackPublic() does not work correctly"); + } + } + } +} + + diff --git a/Lagrange.Core/Common/BotConfig.cs b/Lagrange.Core/Common/BotConfig.cs index 7b6fe23a9..6cac812a4 100644 --- a/Lagrange.Core/Common/BotConfig.cs +++ b/Lagrange.Core/Common/BotConfig.cs @@ -42,6 +42,11 @@ public class BotConfig /// Highway Uploading Concurrency, if the image failed to send, set this to 1 /// public uint HighwayConcurrent { get; set; } = 4; + + /// + /// Refresh the session when the session is about to expired + /// + public bool AutoReLogin { get; set; } = true; } /// diff --git a/Lagrange.Core/Common/Interface/Api/BotExt.cs b/Lagrange.Core/Common/Interface/Api/BotExt.cs index 3c7c38f34..2fdea50cf 100644 --- a/Lagrange.Core/Common/Interface/Api/BotExt.cs +++ b/Lagrange.Core/Common/Interface/Api/BotExt.cs @@ -30,6 +30,9 @@ public static Task LoginByPassword(this BotContext bot) public static bool SubmitCaptcha(this BotContext bot, string ticket, string randStr) => bot.ContextCollection.Business.WtExchangeLogic.SubmitCaptcha(ticket, randStr); + public static Task SetNeedToConfirmSwitch(this BotContext bot, bool needToConfirm) + => bot.ContextCollection.Business.OperationLogic.SetNeedToConfirmSwitch(needToConfirm); + /// /// Use this method to update keystore, so EasyLogin may be preformed next time by using this keystore /// diff --git a/Lagrange.Core/Common/Interface/Api/GroupExt.cs b/Lagrange.Core/Common/Interface/Api/GroupExt.cs index 18677044a..d00cdca49 100644 --- a/Lagrange.Core/Common/Interface/Api/GroupExt.cs +++ b/Lagrange.Core/Common/Interface/Api/GroupExt.cs @@ -98,6 +98,9 @@ public static Task GroupFSMove(this BotContext bot, uint groupUin, string public static Task GroupFSDelete(this BotContext bot, uint groupUin, string fileId) => bot.ContextCollection.Business.OperationLogic.GroupFSDelete(groupUin, fileId); + public static Task GroupFSCreateFolder(this BotContext bot, uint groupUin, string name) + => bot.ContextCollection.Business.OperationLogic.GroupFSCreateFolder(groupUin, name); + public static Task GroupFSUpload(this BotContext bot, uint groupUin, FileEntity fileEntity, string targetDirectory = "/") => bot.ContextCollection.Business.OperationLogic.GroupFSUpload(groupUin, fileEntity, targetDirectory); diff --git a/Lagrange.Core/Common/Interface/Api/OperationExt.cs b/Lagrange.Core/Common/Interface/Api/OperationExt.cs index c21f2acb7..6bd6e9926 100644 --- a/Lagrange.Core/Common/Interface/Api/OperationExt.cs +++ b/Lagrange.Core/Common/Interface/Api/OperationExt.cs @@ -12,9 +12,9 @@ public static class OperationExt /// target BotContext /// force the cache to be refreshed /// - public static Task> FetchFriends(this BotContext bot, bool refreshCache = false) + public static Task> FetchFriends(this BotContext bot, bool refreshCache = false) => bot.ContextCollection.Business.OperationLogic.FetchFriends(refreshCache); - + /// /// Fetch the member list of the group from server or cache /// @@ -24,7 +24,7 @@ public static Task> FetchFriends(this BotContext bot, bool refre /// public static Task> FetchMembers(this BotContext bot, uint groupUin, bool refreshCache = false) => bot.ContextCollection.Business.OperationLogic.FetchMembers(groupUin, refreshCache); - + /// /// Fetch the group list of the account from server or cache /// @@ -60,7 +60,7 @@ public static Task SendMessage(this BotContext bot, MessageChain /// Successfully recalled or not public static Task RecallGroupMessage(this BotContext bot, uint groupUin, MessageResult result) => bot.ContextCollection.Business.OperationLogic.RecallGroupMessage(groupUin, result); - + /// /// Recall the group message by /// @@ -77,7 +77,7 @@ public static Task RecallGroupMessage(this BotContext bot, MessageChain ch /// public static Task?> FetchGroupRequests(this BotContext bot) => bot.ContextCollection.Business.OperationLogic.FetchGroupRequests(); - + /// /// /// @@ -107,13 +107,13 @@ public static Task SetCustomStatus(this BotContext bot, uint faceId, strin public static Task GroupTransfer(this BotContext bot, uint groupUin, uint targetUin) => bot.ContextCollection.Business.OperationLogic.GroupTransfer(groupUin, targetUin); - - public static Task RequestFriend(this BotContext bot, uint targetUin, string message = "", string question = "") - => bot.ContextCollection.Business.OperationLogic.RequestFriend(targetUin, message, question); + + public static Task RequestFriend(this BotContext bot, uint targetUin, string question = "", string message = "") + => bot.ContextCollection.Business.OperationLogic.RequestFriend(targetUin, question, message); public static Task Like(this BotContext bot, uint targetUin, uint count = 1) => bot.ContextCollection.Business.OperationLogic.Like(targetUin, count); - + /// /// Get the client key for all sites /// @@ -129,7 +129,7 @@ public static Task Like(this BotContext bot, uint targetUin, uint count = /// End Sequence of the message public static Task?> GetGroupMessage(this BotContext bot, uint groupUin, uint startSequence, uint endSequence) => bot.ContextCollection.Business.OperationLogic.GetGroupMessage(groupUin, startSequence, endSequence); - + /// /// Get the history message record for private message /// @@ -139,7 +139,7 @@ public static Task Like(this BotContext bot, uint targetUin, uint count = /// number of message to be fetched before timestamp public static Task?> GetRoamMessage(this BotContext bot, uint friendUin, uint timestamp, uint count) => bot.ContextCollection.Business.OperationLogic.GetRoamMessage(friendUin, timestamp, count); - + /// /// Get the history message record for private message /// @@ -151,26 +151,28 @@ public static Task Like(this BotContext bot, uint targetUin, uint count = uint timestamp = (uint)(targetChain.Time - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; return bot.ContextCollection.Business.OperationLogic.GetRoamMessage(targetChain.FriendUin, timestamp, count); } - + public static Task FetchUserInfo(this BotContext bot, uint uin) => bot.ContextCollection.Business.OperationLogic.FetchUserInfo(uin); - + public static Task?> FetchCustomFace(this BotContext bot) => bot.ContextCollection.Business.OperationLogic.FetchCustomFace(); - + public static Task UploadLongMessage(this BotContext bot, List chains) => bot.ContextCollection.Business.OperationLogic.UploadLongMessage(chains); - + public static Task MarkAsRead(this BotContext bot, MessageChain targetChain) { uint timestamp = (uint)(targetChain.Time - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; return bot.ContextCollection.Business.OperationLogic.MarkAsRead(targetChain.GroupUin ?? 0, targetChain.Uid, targetChain.Sequence, timestamp); } - - public static Task UploadFriendFile(this BotContext bot, uint targetUin, FileEntity fileEntity) + + public static Task UploadFriendFile(this BotContext bot, uint targetUin, FileEntity fileEntity) => bot.ContextCollection.Business.OperationLogic.UploadFriendFile(targetUin, fileEntity); - + public static Task FriendPoke(this BotContext bot, uint friendUin) => bot.ContextCollection.Business.OperationLogic.FriendPoke(friendUin); + public static Task?> FetchMarketFaceKey(this BotContext bot, List faceIds) + => bot.ContextCollection.Business.OperationLogic.FetchMarketFaceKey(faceIds); } diff --git a/Lagrange.Core/Event/EventArg/GroupEssenceEvent.cs b/Lagrange.Core/Event/EventArg/GroupEssenceEvent.cs new file mode 100644 index 000000000..2fb068354 --- /dev/null +++ b/Lagrange.Core/Event/EventArg/GroupEssenceEvent.cs @@ -0,0 +1,25 @@ +namespace Lagrange.Core.Event.EventArg; + +public class GroupEssenceEvent : EventBase +{ + public uint GroupUin { get; } + + public uint Sequence { get; } + + public bool IsSet { get; } // 1 设置精华消息, 2 移除精华消息 + + public uint FromUin { get; } + + public uint OperatorUin { get; } + + public GroupEssenceEvent(uint groupUin, uint sequence, uint setFlag, uint fromUin, uint operatorUin) + { + GroupUin = groupUin; + Sequence = sequence; + IsSet = setFlag == 1; + FromUin = fromUin; + OperatorUin = operatorUin; + + EventMessage = $"{nameof(GroupEssenceEvent)}: {GroupUin} | {FromUin} | {OperatorUin} | ({Sequence}) | IsSet: {IsSet}"; + } +} diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/CachingLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/CachingLogic.cs index ec732f935..b564bbed1 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/CachingLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/CachingLogic.cs @@ -110,11 +110,28 @@ private async Task ResolveFriendsUid() { var fetchFriendsEvent = FetchFriendsEvent.Create(); var events = await Collection.Business.SendEvent(fetchFriendsEvent); - var friends = events.Count != 0 ? ((FetchFriendsEvent)events[0]).Friends : new List(); + + if (events.Count != 0) + { + var @event = (FetchFriendsEvent)events[0]; + uint? nextUin = @event.NextUin; - foreach (var friend in friends) _uinToUid.TryAdd(friend.Uin, friend.Uid); - _cachedFriends.Clear(); - _cachedFriends.AddRange(friends); + while (nextUin != null) + { + var next = FetchFriendsEvent.Create(nextUin); + var results = await Collection.Business.SendEvent(next); + @event.Friends.AddRange(((FetchFriendsEvent)results[0]).Friends); + nextUin = ((FetchFriendsEvent)results[0]).NextUin; + } + + foreach (var friend in @event.Friends) _uinToUid.TryAdd(friend.Uin, friend.Uid); + _cachedFriends.Clear(); + _cachedFriends.AddRange(@event.Friends); + } + else + { + Collection.Log.LogWarning(Tag, "Failed to resolve friends uid and cache."); + } } private async Task ResolveMembersUid(uint groupUin) @@ -141,6 +158,7 @@ private async Task ResolveMembersUid(uint groupUin) else { _cachedGroupMembers[groupUin] = new List(); + Collection.Log.LogWarning(Tag, $"Failed to resolve group {groupUin} members uid and cache."); } } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs index 31b2f426b..c5fcef54f 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs @@ -6,6 +6,7 @@ using Lagrange.Core.Internal.Event.Message; using Lagrange.Core.Internal.Event.Notify; using Lagrange.Core.Internal.Event.System; +using Lagrange.Core.Internal.Packets.Message.Notify; using Lagrange.Core.Internal.Service; using Lagrange.Core.Message; using Lagrange.Core.Message.Entity; @@ -27,6 +28,7 @@ namespace Lagrange.Core.Internal.Context.Logic.Implementation; [EventSubscribe(typeof(GroupSysRecallEvent))] [EventSubscribe(typeof(GroupSysRequestJoinEvent))] [EventSubscribe(typeof(GroupSysRequestInvitationEvent))] +[EventSubscribe(typeof(GroupSysEssenceEvent))] [EventSubscribe(typeof(FriendSysRecallEvent))] [EventSubscribe(typeof(FriendSysRequestEvent))] [EventSubscribe(typeof(FriendSysPokeEvent))] @@ -114,6 +116,12 @@ public override async Task Incoming(ProtocolEvent e) Collection.Invoker.PostEvent(decreaseArgs); break; } + case GroupSysEssenceEvent essence: + { + var essenceArgs = new GroupEssenceEvent(essence.GroupUin, essence.Sequence, essence.SetFlag, essence.FromUin, essence.OperatorUin); + Collection.Invoker.PostEvent(essenceArgs); + break; + } case FriendSysRequestEvent info: { var requestArgs = new FriendRequestEvent(info.SourceUin, info.SourceUid, info.Message, info.Source); @@ -198,6 +206,7 @@ public override async Task Outgoing(ProtocolEvent e) { foreach (var chain in chains) { + await ResolveChainMetadata(chain); await ResolveOutgoingChain(chain); await Collection.Highway.UploadResources(chain); } @@ -378,11 +387,17 @@ private async Task ResolveChainMetadata(MessageChain chain) chain.GroupMemberInfo = chain.FriendUin == 0 ? groups.FirstOrDefault(x => x.Uin == Collection.Keystore.Uin) : groups.FirstOrDefault(x => x.Uin == chain.FriendUin); + + chain.Uid ??= chain.GroupMemberInfo?.Uid; } else { var friends = await Collection.Business.CachingLogic.GetCachedFriends(false); - if (friends.FirstOrDefault(x => x.Uin == chain.FriendUin) is { } friend) chain.FriendInfo = friend; + if (friends.FirstOrDefault(x => x.Uin == chain.FriendUin) is { } friend) + { + chain.FriendInfo = friend; + chain.Uid ??= friend.Uid; + } } } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs index 0e32d5272..fba3756b2 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs @@ -13,7 +13,7 @@ namespace Lagrange.Core.Internal.Context.Logic.Implementation; internal class OperationLogic : LogicBase { private const string Tag = nameof(OperationLogic); - + internal OperationLogic(ContextCollection collection) : base(collection) { } public async Task> GetCookies(List domains) @@ -23,12 +23,12 @@ public async Task> GetCookies(List domains) return events.Count != 0 ? ((FetchCookieEvent)events[0]).Cookies : new List(); } - public Task> FetchFriends(bool refreshCache = false) => + public Task> FetchFriends(bool refreshCache = false) => Collection.Business.CachingLogic.GetCachedFriends(refreshCache); - public Task> FetchMembers(uint groupUin, bool refreshCache = false) => + public Task> FetchMembers(uint groupUin, bool refreshCache = false) => Collection.Business.CachingLogic.GetCachedMembers(groupUin, refreshCache); - + public Task> FetchGroups(bool refreshCache) => Collection.Business.CachingLogic.GetCachedGroups(refreshCache); @@ -43,39 +43,39 @@ public async Task MuteGroupMember(uint groupUin, uint targetUin, uint dura { string? uid = await Collection.Business.CachingLogic.ResolveUid(groupUin, targetUin); if (uid == null) return false; - + var muteGroupMemberEvent = GroupMuteMemberEvent.Create(groupUin, duration, uid); var events = await Collection.Business.SendEvent(muteGroupMemberEvent); return events.Count != 0 && ((GroupMuteMemberEvent)events[0]).ResultCode == 0; } - + public async Task MuteGroupGlobal(uint groupUin, bool isMute) { var muteGroupMemberEvent = GroupMuteGlobalEvent.Create(groupUin, isMute); var events = await Collection.Business.SendEvent(muteGroupMemberEvent); return events.Count != 0 && ((GroupMuteGlobalEvent)events[0]).ResultCode == 0; } - + public async Task KickGroupMember(uint groupUin, uint targetUin, bool rejectAddRequest) { string? uid = await Collection.Business.CachingLogic.ResolveUid(groupUin, targetUin); if (uid == null) return false; - + var muteGroupMemberEvent = GroupKickMemberEvent.Create(groupUin, uid, rejectAddRequest); var events = await Collection.Business.SendEvent(muteGroupMemberEvent); return events.Count != 0 && ((GroupKickMemberEvent)events[0]).ResultCode == 0; } - + public async Task SetGroupAdmin(uint groupUin, uint targetUin, bool isAdmin) { string? uid = await Collection.Business.CachingLogic.ResolveUid(groupUin, targetUin); if (uid == null) return false; - + var muteGroupMemberEvent = GroupSetAdminEvent.Create(groupUin, uid, isAdmin); var events = await Collection.Business.SendEvent(muteGroupMemberEvent); return events.Count != 0 && ((GroupSetAdminEvent)events[0]).ResultCode == 0; } - + public async Task RenameGroupMember(uint groupUin, uint targetUin, string targetName) { string? uid = await Collection.Business.CachingLogic.ResolveUid(groupUin, targetUin); @@ -92,7 +92,7 @@ public async Task RenameGroup(uint groupUin, string targetName) var events = await Collection.Business.SendEvent(renameGroupEvent); return events.Count != 0 && ((GroupRenameEvent)events[0]).ResultCode == 0; } - + public async Task RemarkGroup(uint groupUin, string targetRemark) { var renameGroupEvent = GroupRemarkEvent.Create(groupUin, targetRemark); @@ -113,14 +113,14 @@ public async Task FetchGroupFSSpace(uint groupUin) var events = await Collection.Business.SendEvent(groupFSSpaceEvent); return ((GroupFSSpaceEvent)events[0]).TotalSpace - ((GroupFSSpaceEvent)events[0]).UsedSpace; } - + public async Task FetchGroupFSCount(uint groupUin) { var groupFSSpaceEvent = GroupFSCountEvent.Create(groupUin); var events = await Collection.Business.SendEvent(groupFSSpaceEvent); return ((GroupFSCountEvent)events[0]).FileCount; } - + public async Task> FetchGroupFSList(uint groupUin, string targetDirectory, uint startIndex) { var groupFSListEvent = GroupFSListEvent.Create(groupUin, targetDirectory, startIndex); @@ -138,17 +138,24 @@ public async Task FetchGroupFSDownload(uint groupUin, string fileId) public async Task GroupFSMove(uint groupUin, string fileId, string parentDirectory, string targetDirectory) { var groupFSMoveEvent = GroupFSMoveEvent.Create(groupUin, fileId, parentDirectory, targetDirectory); - var events = await Collection.Business.SendEvent(groupFSMoveEvent); + var events = await Collection.Business.SendEvent(groupFSMoveEvent); return events.Count != 0 && ((GroupFSMoveEvent)events[0]).ResultCode == 0; } - + public async Task GroupFSDelete(uint groupUin, string fileId) { var groupFSDeleteEvent = GroupFSDeleteEvent.Create(groupUin, fileId); - var events = await Collection.Business.SendEvent(groupFSDeleteEvent); + var events = await Collection.Business.SendEvent(groupFSDeleteEvent); return events.Count != 0 && ((GroupFSDeleteEvent)events[0]).ResultCode == 0; } - + + public async Task GroupFSCreateFolder(uint groupUin, string name) + { + var groupFSCreateFolderEvent = GroupFSCreateFolderEvent.Create(groupUin, name); + var events = await Collection.Business.SendEvent(groupFSCreateFolderEvent); + return events.Count != 0 && ((GroupFSCreateFolderEvent)events[0]).ResultCode == 0; + } + public Task GroupFSUpload(uint groupUin, FileEntity fileEntity, string targetDirectory) { try @@ -160,12 +167,12 @@ public Task GroupFSUpload(uint groupUin, FileEntity fileEntity, string tar return Task.FromResult(false); } } - + public async Task UploadFriendFile(uint targetUin, FileEntity fileEntity) { string? uid = await Collection.Business.CachingLogic.ResolveUid(null, targetUin); var chain = new MessageChain(targetUin, Collection.Keystore.Uid ?? "", uid ?? "") { fileEntity }; - + try { return await FileUploader.UploadPrivate(Collection, chain, fileEntity); @@ -175,20 +182,20 @@ public async Task UploadFriendFile(uint targetUin, FileEntity fileEntity) return false; } } - + public async Task RecallGroupMessage(uint groupUin, MessageResult result) { if (result.Sequence == null) return false; - + var recallMessageEvent = RecallGroupMessageEvent.Create(groupUin, result.Sequence.Value); var events = await Collection.Business.SendEvent(recallMessageEvent); return events.Count != 0 && ((RecallGroupMessageEvent)events[0]).ResultCode == 0; } - + public async Task RecallGroupMessage(MessageChain chain) { if (chain.GroupUin == null) return false; - + var recallMessageEvent = RecallGroupMessageEvent.Create(chain.GroupUin.Value, chain.Sequence); var events = await Collection.Business.SendEvent(recallMessageEvent); return events.Count != 0 && ((RecallGroupMessageEvent)events[0]).ResultCode == 0; @@ -209,7 +216,7 @@ public async Task RecallGroupMessage(MessageChain chain) uint invitorUin = uins[0]; uint targetUin = uins[1]; uint operatorUin = uins[2]; - + results.Add(new BotGroupRequest( result.GroupUin, invitorUin, @@ -225,11 +232,11 @@ public async Task RecallGroupMessage(MessageChain chain) } return results; - + async Task ResolveUid(string? uid) { if (uid == null) return 0; - + var fetchUidEvent = FetchUserInfoEvent.Create(uid); var e = await Collection.Business.SendEvent(fetchUidEvent); return e.Count == 0 ? 0 : ((FetchUserInfoEvent)e[0]).UserInfo.Uin; @@ -269,17 +276,17 @@ public async Task SetCustomStatus(uint faceId, string text) return results.Count != 0 && results[0].ResultCode == 0; } - public async Task RequestFriend(uint targetUin, string message, string question) + public async Task RequestFriend(uint targetUin, string question, string message) { var requestFriendSearchEvent = RequestFriendSearchEvent.Create(targetUin); var searchEvents = await Collection.Business.SendEvent(requestFriendSearchEvent); if (searchEvents.Count == 0) return false; await Task.Delay(5000); - + var requestFriendSettingEvent = RequestFriendSettingEvent.Create(targetUin); var settingEvents = await Collection.Business.SendEvent(requestFriendSettingEvent); if (settingEvents.Count == 0) return false; - + var requestFriendEvent = RequestFriendEvent.Create(targetUin, message, question); var events = await Collection.Business.SendEvent(requestFriendEvent); return events.Count != 0 && ((RequestFriendEvent)events[0]).ResultCode == 0; @@ -308,7 +315,7 @@ public async Task InviteGroup(uint targetGroupUin, Dictionary var results = await Collection.Business.SendEvent(@event); return results.Count != 0 && results[0].ResultCode == 0; } - + public async Task GetClientKey() { var clientKeyEvent = FetchClientKeyEvent.Create(); @@ -322,37 +329,37 @@ public async Task SetGroupRequest(uint groupUin, ulong sequence, uint type var results = await Collection.Business.SendEvent(inviteEvent); return results.Count != 0 && results[0].ResultCode == 0; } - + public async Task SetFriendRequest(string targetUid, bool accept) { var inviteEvent = SetFriendRequestEvent.Create(targetUid, accept); var results = await Collection.Business.SendEvent(inviteEvent); return results.Count != 0 && results[0].ResultCode == 0; } - + public async Task?> GetGroupMessage(uint groupUin, uint startSequence, uint endSequence) { var getMsgEvent = GetGroupMessageEvent.Create(groupUin, startSequence, endSequence); var results = await Collection.Business.SendEvent(getMsgEvent); return results.Count != 0 ? ((GetGroupMessageEvent)results[0]).Chains : null; } - + public async Task?> GetRoamMessage(uint friendUin, uint time, uint count) { if (await Collection.Business.CachingLogic.ResolveUid(null, friendUin) is not { } uid) return null; - var roamEvent = GetRoamMessageEvent.Create(uid, time, count); + var roamEvent = GetRoamMessageEvent.Create(uid, time, count); var results = await Collection.Business.SendEvent(roamEvent); return results.Count != 0 ? ((GetRoamMessageEvent)results[0]).Chains : null; } - + public async Task?> FetchCustomFace() { var fetchCustomFaceEvent = FetchCustomFaceEvent.Create(); var results = await Collection.Business.SendEvent(fetchCustomFaceEvent); return results.Count != 0 ? ((FetchCustomFaceEvent)results[0]).Urls : null; } - + public async Task UploadLongMessage(List chains) { var multiMsgUploadEvent = MultiMsgUploadEvent.Create(null, chains); @@ -373,7 +380,7 @@ public async Task FriendPoke(uint friendUin) var results = await Collection.Business.SendEvent(friendPokeEvent); return results.Count != 0 && ((FriendPokeEvent)results[0]).ResultCode == 0; } - + public async Task GroupPoke(uint groupUin, uint friendUin) { var friendPokeEvent = GroupPokeEvent.Create(friendUin, groupUin); @@ -387,14 +394,14 @@ public async Task SetEssenceMessage(uint groupUin, uint sequence, uint ran var results = await Collection.Business.SendEvent(setEssenceMessageEvent); return results.Count != 0 && ((SetEssenceMessageEvent)results[0]).ResultCode == 0; } - + public async Task RemoveEssenceMessage(uint groupUin, uint sequence, uint random) { var removeEssenceMessageEvent = RemoveEssenceMessageEvent.Create(groupUin, sequence, random); var results = await Collection.Business.SendEvent(removeEssenceMessageEvent); return results.Count != 0 && ((RemoveEssenceMessageEvent)results[0]).ResultCode == 0; } - + public async Task GroupSetSpecialTitle(uint groupUin, uint targetUin, string title) { string? uid = await Collection.Business.CachingLogic.ResolveUid(groupUin, targetUin); @@ -409,16 +416,30 @@ public async Task GroupSetSpecialTitle(uint groupUin, uint targetUin, stri { string? uid = await Collection.Business.CachingLogic.ResolveUid(null, uin); if (uid == null) return null; - + var fetchUserInfoEvent = FetchUserInfoEvent.Create(uid); var events = await Collection.Business.SendEvent(fetchUserInfoEvent); return events.Count != 0 ? ((FetchUserInfoEvent)events[0]).UserInfo : null; } - + public async Task SetMessageReaction(uint groupUin, uint sequence, string code) { var setReactionEvent = GroupSetReactionEvent.Create(groupUin, sequence, code); var results = await Collection.Business.SendEvent(setReactionEvent); return results.Count != 0 && results[0].ResultCode == 0; } + + public async Task SetNeedToConfirmSwitch(bool enableNoNeed) + { + var setNeedToConfirmSwitchEvent = SetNeedToConfirmSwitchEvent.Create(enableNoNeed); + var results = await Collection.Business.SendEvent(setNeedToConfirmSwitchEvent); + return results.Count != 0 && results[0].ResultCode == 0; + } + + public async Task?> FetchMarketFaceKey(List faceIds) + { + var fetchMarketFaceKeyEvent = FetchMarketFaceKeyEvent.Create(faceIds); + var results = await Collection.Business.SendEvent(fetchMarketFaceKeyEvent); + return results.Count != 0 ? ((FetchMarketFaceKeyEvent)results[0]).Keys : null; + } } diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs index 1755ca81c..afb7b885b 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs @@ -26,6 +26,8 @@ internal class WtExchangeLogic : LogicBase { private const string Tag = nameof(WtExchangeLogic); + private readonly Timer _reLoginTimer; + private readonly TaskCompletionSource _transEmpTask; private TaskCompletionSource<(string, string)>? _captchaTask; @@ -36,6 +38,7 @@ internal class WtExchangeLogic : LogicBase internal WtExchangeLogic(ContextCollection collection) : base(collection) { _transEmpTask = new TaskCompletionSource(); + _reLoginTimer = new Timer(async _ => await ReLogin(), null, Timeout.Infinite, Timeout.Infinite); } public override async Task Incoming(ProtocolEvent e) @@ -408,7 +411,7 @@ private async Task QueryTransEmpState(Func> callback) } - public async Task BotOnline(BotOnlineEvent.OnlineReason reason = BotOnlineEvent.OnlineReason.Login) + public async Task BotOnline(BotOnlineEvent.OnlineReason reason = BotOnlineEvent.OnlineReason.Login) { var registerEvent = StatusRegisterEvent.Create(); var registerResponse = await Collection.Business.SendEvent(registerEvent); @@ -418,14 +421,22 @@ public async Task BotOnline(BotOnlineEvent.OnlineReason reason = BotOnlin { var resp = (StatusRegisterEvent)registerResponse[0]; Collection.Log.LogInfo(Tag, $"Register Status: {resp.Message}"); - Collection.Scheduler.Interval("SsoHeartBeat", (int)(4.5 * 60 * 1000), heartbeatDelegate); - var onlineEvent = new BotOnlineEvent(reason); - Collection.Invoker.PostEvent(onlineEvent); + bool result = resp.Message.Contains("register success"); + if (result) + { + Collection.Scheduler.Interval("SsoHeartBeat", (int)(4.5 * 60 * 1000), heartbeatDelegate); + + var onlineEvent = new BotOnlineEvent(reason); + Collection.Invoker.PostEvent(onlineEvent); - await Collection.Business.PushEvent(InfoSyncEvent.Create()); + await Collection.Business.PushEvent(InfoSyncEvent.Create()); - return resp.Message.Contains("register success"); + _reLoginTimer.Change(TimeSpan.FromDays(15), TimeSpan.FromDays(15)); + Collection.Log.LogInfo(Tag, "AutoReLogin Enabled, session would be refreshed in 15 days period"); + } + + return result; } return false; @@ -452,6 +463,51 @@ private async Task DoUnusualEasyLogin() var result = await Collection.Business.SendEvent(unusualEvent); return result.Count != 0 && ((UnusualEasyLoginEvent)result[0]).Success; } + + private async Task ReLogin() + { + Collection.Log.LogInfo(Tag, "Session is about to expire, try to relogin and refresh"); + if (Collection.Keystore.Session.TempPassword == null) + { + Collection.Log.LogInfo(Tag, "A2 is null, abort"); + return; + } + + var d2 = Collection.Keystore.Session.D2; + var d2Key = Collection.Keystore.Session.D2Key; + var tgt = Collection.Keystore.Session.Tgt; // save the original state + + Collection.Socket.Disconnect(); + Collection.Keystore.ClearSession(); + await Collection.Socket.Connect(); + + if (await KeyExchange()) + { + var easyLoginEvent = EasyLoginEvent.Create(); + var easyLoginResult = await Collection.Business.SendEvent(easyLoginEvent); + if (easyLoginResult.Count != 0) + { + var result = (EasyLoginEvent)easyLoginResult[0]; + if ((LoginCommon.Error)result.ResultCode == LoginCommon.Error.Success) + { + Collection.Log.LogInfo(Tag, "Login Success, try to register services"); + if (await BotOnline(BotOnlineEvent.OnlineReason.Reconnect)) return; + + Collection.Log.LogInfo(Tag, "Re-login failed, please refresh manually"); + } + } + } + else + { + Collection.Log.LogInfo(Tag, "Key Exchange Failed, trying to online, please refresh manually"); + } + + Collection.Keystore.Session.D2 = d2; + Collection.Keystore.Session.D2Key = d2Key; + Collection.Keystore.Session.Tgt = tgt; + + await BotOnline(BotOnlineEvent.OnlineReason.Reconnect); + } public bool SubmitCaptcha(string ticket, string randStr) => _captchaTask?.TrySetResult((ticket, randStr)) ?? false; } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Context/ServiceContext.cs b/Lagrange.Core/Internal/Context/ServiceContext.cs index 2ca916867..386416776 100644 --- a/Lagrange.Core/Internal/Context/ServiceContext.cs +++ b/Lagrange.Core/Internal/Context/ServiceContext.cs @@ -114,6 +114,8 @@ public List ResolveEventByPacket(SsoPacket packet) return result; } + public int GetNewSequence() => _sequenceProvider.GetNewSequence(); + private class SequenceProvider { private readonly ConcurrentDictionary _sessionSequence = new(); diff --git a/Lagrange.Core/Internal/Context/SocketContext.cs b/Lagrange.Core/Internal/Context/SocketContext.cs index eb48f7494..e722f1081 100644 --- a/Lagrange.Core/Internal/Context/SocketContext.cs +++ b/Lagrange.Core/Internal/Context/SocketContext.cs @@ -58,7 +58,9 @@ private async Task Reconnect() return false; } - + + public void Disconnect() => _tcpClient.Disconnect(); + public Task Send(ReadOnlyMemory packet) => _tcpClient.Send(packet); public uint GetPacketLength(ReadOnlySpan header) => BinaryPrimitives.ReadUInt32BigEndian(header); diff --git a/Lagrange.Core/Internal/Event/Action/FetchMarketFaceKeyEvent.cs b/Lagrange.Core/Internal/Event/Action/FetchMarketFaceKeyEvent.cs new file mode 100644 index 000000000..38b5bb946 --- /dev/null +++ b/Lagrange.Core/Internal/Event/Action/FetchMarketFaceKeyEvent.cs @@ -0,0 +1,22 @@ +namespace Lagrange.Core.Internal.Event.Action; + +internal class FetchMarketFaceKeyEvent : ProtocolEvent +{ + public List FaceIds { get; } = new(); + + public List? Keys { get; } = new(); + + private FetchMarketFaceKeyEvent(List faceIds) : base(true) + { + FaceIds = faceIds; + } + + protected FetchMarketFaceKeyEvent(int resultCode, List? keys) : base(resultCode) + { + Keys = keys; + } + + public static FetchMarketFaceKeyEvent Create(List faceIds) => new(faceIds); + + public static FetchMarketFaceKeyEvent Result(int resultCode, List? keys) => new(resultCode, keys); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/Action/GroupFSCreateFolderEvent.cs b/Lagrange.Core/Internal/Event/Action/GroupFSCreateFolderEvent.cs new file mode 100644 index 000000000..d0d99bb2a --- /dev/null +++ b/Lagrange.Core/Internal/Event/Action/GroupFSCreateFolderEvent.cs @@ -0,0 +1,20 @@ +namespace Lagrange.Core.Internal.Event.Action; + +internal class GroupFSCreateFolderEvent : ProtocolEvent +{ + public uint GroupUin { get; } + + public string Name { get; } = string.Empty; + + private GroupFSCreateFolderEvent(uint groupUin, string name) : base(true) + { + GroupUin = groupUin; + Name = name; + } + + private GroupFSCreateFolderEvent(int resultCode) : base(resultCode) { } + + public static GroupFSCreateFolderEvent Create(uint groupUin, string name) => new(groupUin, name); + + public static GroupFSCreateFolderEvent Result(int resultCode) => new(resultCode); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/Notify/GroupSysEssenceEvent.cs b/Lagrange.Core/Internal/Event/Notify/GroupSysEssenceEvent.cs new file mode 100644 index 000000000..a92d932bd --- /dev/null +++ b/Lagrange.Core/Internal/Event/Notify/GroupSysEssenceEvent.cs @@ -0,0 +1,26 @@ +namespace Lagrange.Core.Internal.Event.Notify; + +internal class GroupSysEssenceEvent : ProtocolEvent +{ + public uint GroupUin { get; } + + public uint Sequence { get; } + + public uint SetFlag { get; } // 1 设置精华消息, 2 移除精华消息 + + public uint FromUin { get; } + + public uint OperatorUin { get; } + + private GroupSysEssenceEvent(uint groupUin, uint sequence, uint setFlag, uint fromUin, uint operatorUin) : base(0) + { + GroupUin = groupUin; + Sequence = sequence; + SetFlag = setFlag; + FromUin = fromUin; + OperatorUin = operatorUin; + } + + public static GroupSysEssenceEvent Result(uint groupUin, uint sequence, uint setFlag, uint fromUin, uint operatorUin) + => new(groupUin, sequence, setFlag, fromUin, operatorUin); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/System/FetchFriendsEvent.cs b/Lagrange.Core/Internal/Event/System/FetchFriendsEvent.cs index 3c0a2c7f0..1d8330feb 100644 --- a/Lagrange.Core/Internal/Event/System/FetchFriendsEvent.cs +++ b/Lagrange.Core/Internal/Event/System/FetchFriendsEvent.cs @@ -6,16 +6,21 @@ internal class FetchFriendsEvent : ProtocolEvent { public List Friends { get; } = new(); - private FetchFriendsEvent() : base(true) + public uint? NextUin { get; } + + private FetchFriendsEvent(uint? nextUin) : base(true) { + NextUin = nextUin; } - private FetchFriendsEvent(int resultCode, List friends) : base(resultCode) + private FetchFriendsEvent(int resultCode, List friends, uint? nextUin) : base(resultCode) { Friends = friends; + NextUin = nextUin; } - public static FetchFriendsEvent Create() => new(); + public static FetchFriendsEvent Create(uint? nextUin = null) => new(nextUin); - public static FetchFriendsEvent Result(int resultCode, List friends) => new(resultCode, friends); + public static FetchFriendsEvent Result(int resultCode, List friends, uint? nextUin) => + new(resultCode, friends, nextUin); } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/System/SetNeedToConfirmSwitchEvent.cs b/Lagrange.Core/Internal/Event/System/SetNeedToConfirmSwitchEvent.cs new file mode 100644 index 000000000..395b68edd --- /dev/null +++ b/Lagrange.Core/Internal/Event/System/SetNeedToConfirmSwitchEvent.cs @@ -0,0 +1,18 @@ +namespace Lagrange.Core.Internal.Event.System; + +internal class SetNeedToConfirmSwitchEvent : ProtocolEvent +{ + public bool EnableNoNeed { get; } + + + private SetNeedToConfirmSwitchEvent(bool enableNoNeed) : base(true) + { + EnableNoNeed = enableNoNeed; + } + + private SetNeedToConfirmSwitchEvent(int resultCode) : base(resultCode) { } + + public static SetNeedToConfirmSwitchEvent Create(bool enableNoNeed) => new(enableNoNeed); + + public static SetNeedToConfirmSwitchEvent Result(int resultCode) => new(resultCode); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Action/MarketFaceKeyRequest.cs b/Lagrange.Core/Internal/Packets/Action/MarketFaceKeyRequest.cs new file mode 100644 index 000000000..70e9a4aec --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Action/MarketFaceKeyRequest.cs @@ -0,0 +1,19 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Action; + +#pragma warning disable CS8618 + +[ProtoContract] +internal class MarketFaceKeyRequest +{ + [ProtoMember(1)] public uint Field1 { get; set; } + + [ProtoMember(5)] public MarketFaceInfo Info { get; set; } +} + +[ProtoContract] +internal class MarketFaceInfo +{ + [ProtoMember(3)] public List FaceIds { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Action/MarketFaceKeyResponse.cs b/Lagrange.Core/Internal/Packets/Action/MarketFaceKeyResponse.cs new file mode 100644 index 000000000..b8b8ceebd --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Action/MarketFaceKeyResponse.cs @@ -0,0 +1,17 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Action; + +#pragma warning disable CS8618 + +[ProtoContract] +internal class MarketFaceKeyResponse +{ + [ProtoMember(5)] public MarketFaceKeyInfo Info { get; set; } +} + +[ProtoContract] +internal class MarketFaceKeyInfo +{ + [ProtoMember(1)] public List Keys { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/Login.cs b/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/Login.cs index 3d60bb197..0d6430763 100644 --- a/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/Login.cs +++ b/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/Login.cs @@ -21,14 +21,10 @@ internal class Login : WtLoginBase public Login(BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device) : base(PacketCommand, WtLoginCommand, keystore, appInfo, device) { } - protected override BinaryPacket ConstructBody() - { - var packet = new BinaryPacket() - .WriteUshort(InternalCommand) - .WritePacket(TlvPacker.Pack(ConstructTlvs)); - - return packet; - } + protected override BinaryPacket ConstructData() => new BinaryPacket() + .WriteUshort(InternalCommand) + .WritePacket(TlvPacker.Pack(ConstructTlvs)); + public static Dictionary Deserialize(BinaryPacket packet, BotKeystore keystore, out State state) { diff --git a/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp.cs b/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp.cs index e75ad72d2..de3e65f79 100644 --- a/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp.cs +++ b/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp.cs @@ -16,35 +16,35 @@ protected TransEmp(ushort qrCmd, BotKeystore keystore, BotAppInfo appInfo, BotDe _qrCodeCommand = qrCmd; } - protected override BinaryPacket ConstructBody() + protected override BinaryPacket ConstructData() { - var packet = new BinaryPacket().WriteByte(0); // known const - - packet.Barrier(w => - { - w.WriteUint((uint)AppInfo.AppId) - .WriteUint(0x00000072) // const - .WriteUshort(0) // const 0 - .WriteByte(0) // const 0 - .WriteUint((uint)DateTimeOffset.Now.ToUnixTimeSeconds()) // length actually starts here - .WriteByte(0x02) // header for packet, counted into length of next barrier manually - .Barrier(w => w - .WriteUshort(_qrCodeCommand) - .WriteUlong(0) // const 0 - .WriteUint(0) // const 0 - .WriteUlong(0) // const 0 - .WriteUshort(3) // const 3 - .WriteUshort(0) // const 0 - .WriteUshort(50) // unknown const - .WriteUlong(0) - .WriteUint(0) - .WriteUshort(0) - .WriteUint((uint)AppInfo.AppId) - .WritePacket(ConstructTransEmp()), Prefix.Uint16 | Prefix.WithPrefix, 1); // addition is the packet start counted in + var tlv = ConstructTlv(); - }, Prefix.Uint16 | Prefix.WithPrefix, -13); // -13 is the length of zeros, which could be found at TransEmp31 and TransEmp12.ConstructTransEmp() + var newPacket = new BinaryPacket() // length of WriteUshort(43 + tlv.Length + 1) refers to this section + .WriteByte(2) + .WriteUshort((ushort)(43 + tlv.Length + 1)) // _head_len = 43 + data.size +1 + .WriteUshort(_qrCodeCommand) + .WriteBytes(new byte[21]) + .WriteByte(0x03) + .WriteShort(0x00) // close + .WriteShort(0x32) // Version Code: 50 + .WriteUint(0) // trans_emp sequence + .WriteUlong(0) // dummy uin + .WritePacket(tlv) + .WriteByte(3); - return packet; + var requestBody = new BinaryPacket() + .WriteUint((uint)DateTimeOffset.Now.ToUnixTimeSeconds()) + .WritePacket(newPacket); + + return new BinaryPacket() + .WriteByte(0x00) // encryptMethod == EncryptMethod.EM_ST || encryptMethod == EncryptMethod.EM_ECDH_ST + .WriteUshort((ushort)requestBody.Length) + .WriteUint((uint)AppInfo.AppId) + .WriteUint(0x72) // Role + .WriteBytes(Array.Empty(), Prefix.Uint16 | Prefix.LengthOnly) // uSt + .WriteBytes(Array.Empty(), Prefix.Uint8 | Prefix.LengthOnly) // rollback + .WritePacket(requestBody); } public static BinaryPacket DeserializeBody(BotKeystore keystore, BinaryPacket packet, out ushort command) @@ -60,5 +60,5 @@ public static BinaryPacket DeserializeBody(BotKeystore keystore, BinaryPacket pa return packet; } - protected abstract BinaryPacket ConstructTransEmp(); + protected abstract BinaryPacket ConstructTlv(); } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp12.cs b/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp12.cs index 26624b11b..abc33d5bb 100644 --- a/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp12.cs +++ b/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp12.cs @@ -11,16 +11,18 @@ internal class TransEmp12 : TransEmp public TransEmp12(BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device) : base(QrCodeCommand, keystore, appInfo, device) { } - protected override BinaryPacket ConstructTransEmp() + protected override BinaryPacket ConstructTlv() { if (Keystore.Session.QrSign != null) { return new BinaryPacket() + .WriteUshort(0) + .WriteUint((uint)AppInfo.AppId) .WriteBytes(Keystore.Session.QrSign, Prefix.Uint16 | Prefix.LengthOnly) - .WriteUlong(0) // const 0 - .WriteUint(0) // const 0 - .WriteByte(0) // const 0 - .WriteByte(0x03); // packet end + .WriteUlong(0) // uin + .WriteByte(0) // version + .WriteBytes(Array.Empty(), Prefix.Uint16 | Prefix.LengthOnly) + .WriteShort(0); } throw new Exception("QrSign is null"); diff --git a/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp31.cs b/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp31.cs index baf7edbda..4b915b054 100644 --- a/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp31.cs +++ b/Lagrange.Core/Internal/Packets/Login/WtLogin/Entity/TransEmp31.cs @@ -21,12 +21,14 @@ internal class TransEmp31 : TransEmp public TransEmp31(BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device) : base(QrCodeCommand, keystore, appInfo, device) { } - protected override BinaryPacket ConstructTransEmp() => new BinaryPacket() + protected override BinaryPacket ConstructTlv() => new BinaryPacket() .WriteUshort(0) - .WriteUlong(0) + .WriteUint((uint)AppInfo.AppId) + .WriteUlong(0) // uin + .WriteBytes(Array.Empty()) // TGT .WriteByte(0) - .WritePacket(TlvPacker.PackQrCode(Keystore.Session.UnusualSign == null ? ConstructTlvs : ConstructTlvsPassword)) - .WriteByte(0x03); + .WriteBytes(Array.Empty(), Prefix.Uint16 | Prefix.LengthOnly) + .WritePacket(TlvPacker.PackQrCode(Keystore.Session.UnusualSign == null ? ConstructTlvs : ConstructTlvsPassword)); public static Dictionary Deserialize(BinaryPacket packet, BotKeystore keystore, out byte[] signature) { diff --git a/Lagrange.Core/Internal/Packets/Login/WtLogin/WtLoginBase.cs b/Lagrange.Core/Internal/Packets/Login/WtLogin/WtLoginBase.cs index 92f1d5a32..83f74fee9 100644 --- a/Lagrange.Core/Internal/Packets/Login/WtLogin/WtLoginBase.cs +++ b/Lagrange.Core/Internal/Packets/Login/WtLogin/WtLoginBase.cs @@ -26,7 +26,7 @@ protected WtLoginBase(string command, ushort cmd, BotKeystore keystore, BotAppIn public BinaryPacket ConstructPacket() { - var body = ConstructBody(); + var body = ConstructData(); var encrypt = Keystore.SecpImpl.Encrypt(body.ToArray()); var packet = new BinaryPacket() @@ -43,14 +43,12 @@ public BinaryPacket ConstructPacket() .WriteUshort(0) // insId .WriteUshort(AppInfo.AppClientVersion) // cliType .WriteUint(0) // retryTime - .WriteByte(1) // const - .WriteByte(1) // const - .WriteBytes(Keystore.Stub.RandomKey.AsSpan()) // randKey - .WriteUshort(0x102) // unknown const, 腾讯你妈妈死啦 - .WriteBytes(Keystore.SecpImpl.GetPublicKey(), Prefix.Uint16 | Prefix.LengthOnly) // pubKey + .WritePacket(BuildEncryptHead()) .WriteBytes(encrypt.AsSpan()) .WriteByte(3), Prefix.Uint16 | Prefix.WithPrefix, 1); // 0x03 is the packet end + // for the addition of 1, the packet start should be counted in + return packet; } @@ -74,5 +72,12 @@ protected static BinaryPacket DeserializePacket(BotKeystore keystore, BinaryPack return decrypted; } - protected abstract BinaryPacket ConstructBody(); + protected abstract BinaryPacket ConstructData(); + + private BinaryPacket BuildEncryptHead() => new BinaryPacket() + .WriteByte(1) // const + .WriteByte(1) // const + .WriteBytes(Keystore.Stub.RandomKey.AsSpan()) // randKey + .WriteUshort(0x102) // unknown const, 腾讯你妈妈死啦 + .WriteBytes(Keystore.SecpImpl.GetPublicKey(), Prefix.Uint16 | Prefix.LengthOnly); // pubKey } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Message/Element/Elem.cs b/Lagrange.Core/Internal/Packets/Message/Element/Elem.cs index 0308f3b8d..c5d12bf78 100644 --- a/Lagrange.Core/Internal/Packets/Message/Element/Elem.cs +++ b/Lagrange.Core/Internal/Packets/Message/Element/Elem.cs @@ -18,7 +18,7 @@ internal class Elem [ProtoMember(5)] public TransElem? TransElem { get; set; } - [ProtoMember(6)] public MarketFace? MarketFace { get; set; } + [ProtoMember(6)] public Marketface? Marketface { get; set; } [ProtoMember(8)] public CustomFace? CustomFace { get; set; } diff --git a/Lagrange.Core/Internal/Packets/Message/Element/Implementation/MarketFace.cs b/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Marketface.cs similarity index 86% rename from Lagrange.Core/Internal/Packets/Message/Element/Implementation/MarketFace.cs rename to Lagrange.Core/Internal/Packets/Message/Element/Implementation/Marketface.cs index 6c0fc6f1f..f80137890 100644 --- a/Lagrange.Core/Internal/Packets/Message/Element/Implementation/MarketFace.cs +++ b/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Marketface.cs @@ -6,7 +6,7 @@ namespace Lagrange.Core.Internal.Packets.Message.Element.Implementation; [ProtoContract] -internal class MarketFace +internal class Marketface { [ProtoMember(1)] public string Summary { get; set; } @@ -26,11 +26,11 @@ internal class MarketFace [ProtoMember(11)] public int Height { get; set; } - [ProtoMember(13)] public MarketFaceReserve PbReserve { get; set; } + [ProtoMember(13)] public MarketfaceReserve PbReserve { get; set; } } [ProtoContract] -internal class MarketFaceReserve +internal class MarketfaceReserve { [ProtoMember(8)] public int Field8 { get; set; } diff --git a/Lagrange.Core/Internal/Packets/Message/Notify/GroupEssenceMessage.cs b/Lagrange.Core/Internal/Packets/Message/Notify/GroupEssenceMessage.cs new file mode 100644 index 000000000..bac4b95df --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Message/Notify/GroupEssenceMessage.cs @@ -0,0 +1,44 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Message.Notify; +#pragma warning disable CS8618 + +[ProtoContract] +internal class GroupEssenceMessage +{ + // [ProtoMember(4)] public uint GroupUin; 有两个4 + + [ProtoMember(13)] public uint Field13; + + [ProtoMember(33)] public EssenceMessage EssenceMessage; + + [ProtoMember(37)] public uint MsgSequence; + + [ProtoMember(39)] public uint Field39; +} + +[ProtoContract] +internal class EssenceMessage +{ + [ProtoMember(1)] public uint GroupUin; + + [ProtoMember(2)] public uint MsgSequence; + + [ProtoMember(3)] public uint Random; + + [ProtoMember(4)] public uint SetFlag; // set 1 remove 2 + + [ProtoMember(5)] public uint MemberUin; + + [ProtoMember(6)] public uint OperatorUin; + + [ProtoMember(7)] public uint TimeStamp; + + [ProtoMember(8)] public uint MsgSequence2; // removed 0 + + [ProtoMember(9)] public string OperatorNickName; + + [ProtoMember(10)] public string MemberNickName; + + [ProtoMember(11)] public uint SetFlag2; +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/InfoPushGroup.cs b/Lagrange.Core/Internal/Packets/Service/InfoPushGroup.cs deleted file mode 100644 index e8c982ce7..000000000 --- a/Lagrange.Core/Internal/Packets/Service/InfoPushGroup.cs +++ /dev/null @@ -1,28 +0,0 @@ -using ProtoBuf; - -namespace Lagrange.Core.Internal.Packets.Service; - -/// -/// Body for trpc.msg.register_proxy.RegisterProxy.InfoSyncPush type 5, as List<InfoPushGroup> -/// -[ProtoContract] -internal class InfoPushGroup -{ - [ProtoMember(1)] public uint GroupUin { get; set; } - - [ProtoMember(2)] public uint Sequence1 { get; set; } - - [ProtoMember(3)] public uint Sequence2 { get; set; } - - [ProtoMember(4)] public uint Field4 { get; set; } - - [ProtoMember(8)] public uint LastMsgTime { get; set; } - - [ProtoMember(9)] public string GroupName { get; set; } = ""; - - [ProtoMember(10)] public uint Sequence3 { get; set; } - - [ProtoMember(11)] public ulong Random { get; set; } - - [ProtoMember(13)] public uint Field13 { get; set; } -} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Generics/OidbFriend.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Generics/OidbFriend.cs index 4b74b7302..d1fc9f4c5 100644 --- a/Lagrange.Core/Internal/Packets/Service/Oidb/Generics/OidbFriend.cs +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Generics/OidbFriend.cs @@ -8,6 +8,8 @@ internal class OidbFriend { [ProtoMember(1)] public string Uid { get; set; } + [ProtoMember(2)] public uint CustomGroup { get; set; } + [ProtoMember(3)] public uint Uin { get; set; } [ProtoMember(10001)] public List Additional { get; set; } diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x1277_0.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x1277_0.cs new file mode 100644 index 000000000..c52fd20d2 --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x1277_0.cs @@ -0,0 +1,34 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request; + +#pragma warning disable CS8618 +// ReSharper disable InconsistentNaming + +[ProtoContract] +[OidbSvcTrpcTcp(0x1277, 0)] +internal class OidbSvcTrpcTcp0x1277_0 +{ + [ProtoMember(1)] public OidbSvcTrpcTcp0x1277_0Body Body { get; set; } +} + + +[ProtoContract] +internal class OidbSvcTrpcTcp0x1277_0Body +{ + [ProtoMember(1)] public OidbSvcTrpcTcp0x1277_0Device Device { get; set; } + + [ProtoMember(2)] public bool GuidEncryptedType { get; set; } + + [ProtoMember(3)] public bool AutoLogin { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x1277_0Device +{ + [ProtoMember(1)] public byte[] Guid { get; set; } + + [ProtoMember(2)] public uint AppId { get; set; } + + [ProtoMember(3)] public string PackageName { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_0.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_0.cs new file mode 100644 index 000000000..1a3c299f0 --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_0.cs @@ -0,0 +1,26 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request; + +#pragma warning disable CS8618 +// ReSharper disable InconsistentNaming + +/// +/// Create Folder +/// +[ProtoContract] +[OidbSvcTrpcTcp(0x6D7, 9)] +internal class OidbSvcTrpcTcp0x6D7_0 +{ + [ProtoMember(1)] public OidbSvcTrpcTcp0x6D7_0Folder Folder { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x6D7_0Folder +{ + [ProtoMember(1)] public uint GroupUin { get; set; } + + [ProtoMember(3)] public string RootDirectory { get; set; } + + [ProtoMember(4)] public string FolderName { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0xFD4_1.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0xFD4_1.cs index 48118fe9a..6134b99de 100644 --- a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0xFD4_1.cs +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0xFD4_1.cs @@ -13,11 +13,15 @@ namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request; [OidbSvcTrpcTcp(0xfd4, 1)] internal class OidbSvcTrpcTcp0xFD4_1 { - [ProtoMember(2)] public uint Field2 { get; set; } = 300; + [ProtoMember(2)] public uint FriendCount { get; set; } = 300; // paging get num [ProtoMember(4)] public uint Field4 { get; set; } = 0; + [ProtoMember(5)] public OidbSvcTrpcTcp0xFD4_1Uin NextUin { get; set; } + [ProtoMember(6)] public uint Field6 { get; set; } = 1; + + [ProtoMember(7)] public uint Field7 { get; set; } = int.MaxValue; [ProtoMember(10001)] public List Body { get; set; } @@ -32,4 +36,10 @@ internal class OidbSvcTrpcTcp0xFD4_1Body [ProtoMember(1)] public uint Type { get; set; } [ProtoMember(2)] public OidbNumber Number { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0xFD4_1Uin +{ + [ProtoMember(1)] public uint Uin { get; set; } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0xFD4_1Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0xFD4_1Response.cs index 1267a042e..383631951 100644 --- a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0xFD4_1Response.cs +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0xFD4_1Response.cs @@ -9,6 +9,8 @@ namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response; [ProtoContract] internal class OidbSvcTrpcTcp0xFD4_1Response { + [ProtoMember(2)] public OidbSvcTrpcTcp0xFD4_1ResponseUin? Next { get; set; } + [ProtoMember(3)] public uint DisplayFriendCount { get; set; } [ProtoMember(6)] public uint Timestamp { get; set; } @@ -16,4 +18,10 @@ internal class OidbSvcTrpcTcp0xFD4_1Response [ProtoMember(7)] public uint SelfUin { get; set; } [ProtoMember(101)] public List Friends { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0xFD4_1ResponseUin +{ + [ProtoMember(1)] public uint Uin { get; set; } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/SsoPacker.cs b/Lagrange.Core/Internal/Packets/SsoPacker.cs index 880fffb7a..188c61720 100644 --- a/Lagrange.Core/Internal/Packets/SsoPacker.cs +++ b/Lagrange.Core/Internal/Packets/SsoPacker.cs @@ -62,10 +62,12 @@ public static SsoPacket Parse(BinaryPacket packet) int retCode = packet.ReadInt(); string extra = packet.ReadString(Prefix.Uint32 | Prefix.WithPrefix); string command = packet.ReadString(Prefix.Uint32 | Prefix.WithPrefix); - packet.ReadString(Prefix.Uint32 | Prefix.WithPrefix); // unknown - int isCompressed = packet.ReadInt(); - packet.ReadBytes(Prefix.Uint32 | Prefix.LengthOnly); // Dummy Sso header - + int msgCookieLength = packet.ReadInt() - 4; + var msgCookie = packet.ReadBytes(msgCookieLength); + int isCompressed = packet.ReadInt(); + int reserveFieldLength = packet.ReadInt(); + var reserveField = packet.ReadBytes(reserveFieldLength); + return retCode == 0 ? new SsoPacket(12, command, sequence, isCompressed == 0 ? packet : InflatePacket(packet)) : new SsoPacket(12, command, sequence, retCode, extra); diff --git a/Lagrange.Core/Internal/Service/Action/FatchMarketFaceKeyService.cs b/Lagrange.Core/Internal/Service/Action/FatchMarketFaceKeyService.cs new file mode 100644 index 000000000..715989ac6 --- /dev/null +++ b/Lagrange.Core/Internal/Service/Action/FatchMarketFaceKeyService.cs @@ -0,0 +1,39 @@ +using Lagrange.Core.Common; +using Lagrange.Core.Internal.Event; +using Lagrange.Core.Internal.Event.Action; +using Lagrange.Core.Internal.Packets.Action; +using Lagrange.Core.Utility.Binary; +using Lagrange.Core.Utility.Extension; +using ProtoBuf; + +namespace Lagrange.Core.Internal.Service.Action; + +[EventSubscribe(typeof(FetchMarketFaceKeyEvent))] +[Service("BQMallSvc.TabOpReq")] +internal class FatchMarketFaceKeyService : BaseService +{ + protected override bool Build(FetchMarketFaceKeyEvent input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, out BinaryPacket output, out List? extraPackets) + { + var packet = new MarketFaceKeyRequest + { + Field1 = 3, + Info = new() + { + FaceIds = input.FaceIds + }, + }; + + output = packet.Serialize(); + extraPackets = null; + return true; + } + + protected override bool Parse(Span input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, out FetchMarketFaceKeyEvent output, out List? extraEvents) + { + var payload = Serializer.Deserialize(input); + + output = FetchMarketFaceKeyEvent.Result(0, payload.Info.Keys); + extraEvents = null; + return true; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/Action/GroupFSCreateFolderService.cs b/Lagrange.Core/Internal/Service/Action/GroupFSCreateFolderService.cs new file mode 100644 index 000000000..d1880579e --- /dev/null +++ b/Lagrange.Core/Internal/Service/Action/GroupFSCreateFolderService.cs @@ -0,0 +1,43 @@ +using Lagrange.Core.Common; +using Lagrange.Core.Internal.Event; +using Lagrange.Core.Internal.Event.Action; +using Lagrange.Core.Internal.Packets.Service.Oidb; +using Lagrange.Core.Internal.Packets.Service.Oidb.Request; +using Lagrange.Core.Utility.Binary; +using Lagrange.Core.Utility.Extension; +using ProtoBuf; + +namespace Lagrange.Core.Internal.Service.Action; + +[EventSubscribe(typeof(GroupFSCreateFolderEvent))] +[Service("OidbSvcTrpcTcp.0x6d7_0")] +internal class GroupFSCreateFolderService : BaseService +{ + protected override bool Build(GroupFSCreateFolderEvent input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out BinaryPacket output, out List? extraPackets) + { + var packet = new OidbSvcTrpcTcpBase(new OidbSvcTrpcTcp0x6D7_0 + { + Folder = new OidbSvcTrpcTcp0x6D7_0Folder + { + GroupUin = input.GroupUin, + RootDirectory = "/", + FolderName = input.Name + } + }, false, true); + + output = packet.Serialize(); + extraPackets = null; + return true; + } + + protected override bool Parse(Span input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out GroupFSCreateFolderEvent output, out List? extraEvents) + { + var packet = Serializer.Deserialize>(input); + + output = GroupFSCreateFolderEvent.Result((int)packet.ErrorCode); + extraEvents = null; + return true; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/Message/PushMessageService.cs b/Lagrange.Core/Internal/Service/Message/PushMessageService.cs index d2f6a61bd..d4a3ce296 100644 --- a/Lagrange.Core/Internal/Service/Message/PushMessageService.cs +++ b/Lagrange.Core/Internal/Service/Message/PushMessageService.cs @@ -151,6 +151,15 @@ private static void ProcessEvent0x2DC(Span payload, PushMsg msg, List(content.AsSpan()); + var essenceMsg = essence.EssenceMessage; + var groupEssenceEvent = GroupSysEssenceEvent.Result(essenceMsg.GroupUin, essenceMsg.MsgSequence, + essenceMsg.SetFlag, essenceMsg.MemberUin, essenceMsg.OperatorUin); + extraEvents.Add(groupEssenceEvent); + break; + } default: { Console.WriteLine($"Unknown Event0x2DC message type: {pkgType}: {payload.Hex()}"); @@ -227,7 +236,8 @@ private enum PkgType private enum Event0x2DCSubType { GroupRecallNotice = 17, - GroupMuteNotice = 12 + GroupMuteNotice = 12, + GroupEssenceNotice = 21 } private enum Event0x210SubType diff --git a/Lagrange.Core/Internal/Service/System/FetchFriendsService.cs b/Lagrange.Core/Internal/Service/System/FetchFriendsService.cs index b49d4e287..f613f9e3f 100644 --- a/Lagrange.Core/Internal/Service/System/FetchFriendsService.cs +++ b/Lagrange.Core/Internal/Service/System/FetchFriendsService.cs @@ -16,17 +16,22 @@ namespace Lagrange.Core.Internal.Service.System; [Service("OidbSvcTrpcTcp.0xfd4_1")] internal class FetchFriendsService : BaseService { + private const int MaxFriendCount = 300; + protected override bool Build(FetchFriendsEvent input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, out BinaryPacket output, out List? extraPackets) { var packet = new OidbSvcTrpcTcpBase(new OidbSvcTrpcTcp0xFD4_1 { + FriendCount = MaxFriendCount, // max value Body = new List { new() { Type = 1, Number = new OidbNumber { Numbers = { 103, 102, 20002 } } }, new() { Type = 4, Number = new OidbNumber { Numbers = { 100, 101, 102 } } } } }); + + if (input.NextUin != null) packet.Body.NextUin = new OidbSvcTrpcTcp0xFD4_1Uin { Uin = input.NextUin.Value }; /* * OidbNumber里面的东西代表你想要拿到的Property,这些Property将会在返回的数据里面的Preserve的Field, @@ -52,8 +57,8 @@ protected override bool Parse(Span input, BotKeystore keystore, BotAppInfo var properties = Property(additional.Layer1.Properties); friends.Add(new BotFriend(raw.Uin, raw.Uid, properties[20002], properties[103], properties[102])); } - - output = FetchFriendsEvent.Result(0, friends); + + output = FetchFriendsEvent.Result(0, friends, packet.Body.Next?.Uin); // 全家4完了才能想出来这种分页的逻辑 extraEvents = null; return true; } diff --git a/Lagrange.Core/Internal/Service/System/SetNeedToConfirmSwitchService.cs b/Lagrange.Core/Internal/Service/System/SetNeedToConfirmSwitchService.cs new file mode 100644 index 000000000..007c9cb52 --- /dev/null +++ b/Lagrange.Core/Internal/Service/System/SetNeedToConfirmSwitchService.cs @@ -0,0 +1,48 @@ +using Lagrange.Core.Common; +using Lagrange.Core.Internal.Event; +using Lagrange.Core.Internal.Event.System; +using Lagrange.Core.Internal.Packets.Service.Oidb; +using Lagrange.Core.Internal.Packets.Service.Oidb.Request; +using Lagrange.Core.Utility.Binary; +using Lagrange.Core.Utility.Extension; +using ProtoBuf; + +namespace Lagrange.Core.Internal.Service.System; + +[EventSubscribe(typeof(SetNeedToConfirmSwitchEvent))] +[Service("OidbSvcTrpcTcp.0x1277_0")] +internal class SetNeedToConfirmSwitchService : BaseService +{ + protected override bool Build(SetNeedToConfirmSwitchEvent input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out BinaryPacket output, out List? extraPackets) + { + var packet = new OidbSvcTrpcTcpBase(new OidbSvcTrpcTcp0x1277_0 + { + Body = new OidbSvcTrpcTcp0x1277_0Body + { + Device = new OidbSvcTrpcTcp0x1277_0Device + { + Guid = device.Guid.ToByteArray(), + AppId = (uint)appInfo.AppId, + PackageName = appInfo.PackageName + }, + GuidEncryptedType = false, + AutoLogin = input.EnableNoNeed + } + }, false, true); + + output = packet.Serialize(); + extraPackets = null; + return true; + } + + protected override bool Parse(Span input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out SetNeedToConfirmSwitchEvent output, out List? extraEvents) + { + var payload = Serializer.Deserialize>(input); + + output = SetNeedToConfirmSwitchEvent.Result((int)payload.ErrorCode); + extraEvents = null; + return true; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Lagrange.Core.csproj b/Lagrange.Core/Lagrange.Core.csproj index c7e95d255..16f5cd900 100644 --- a/Lagrange.Core/Lagrange.Core.csproj +++ b/Lagrange.Core/Lagrange.Core.csproj @@ -10,7 +10,7 @@ The Implementation of NTQQ for Pure C#, Event Driven, derived from Konata.Core 10 true - 0.2.1 + 0.2.3 Linwenxuan04 2023 https://github.com/LagrangeDev/Lagrange.Core git diff --git a/Lagrange.Core/Message/Entity/MarketFaceEntity.cs b/Lagrange.Core/Message/Entity/MarketfaceEntity.cs similarity index 55% rename from Lagrange.Core/Message/Entity/MarketFaceEntity.cs rename to Lagrange.Core/Message/Entity/MarketfaceEntity.cs index b142e1eac..38eea96ef 100644 --- a/Lagrange.Core/Message/Entity/MarketFaceEntity.cs +++ b/Lagrange.Core/Message/Entity/MarketfaceEntity.cs @@ -5,23 +5,23 @@ namespace Lagrange.Core.Message.Entity; using Lagrange.Core.Internal.Packets.Message.Element.Implementation; using Lagrange.Core.Utility.Extension; -[MessageElement(typeof(MarketFace))] -public class MarketFaceEntity : IMessageEntity +[MessageElement(typeof(Marketface))] +public class MarketfaceEntity : IMessageEntity { - public string FaceId { get; } + public string EmojiId { get; set; } - public int TabId { get; } + public int EmojiPackageId { get; set; } - public string Key { get; } + public string Key { get; set; } - public string Summary { get; } + public string Summary { get; set; } - public MarketFaceEntity() : this(string.Empty, default, string.Empty, string.Empty) { } + public MarketfaceEntity() : this(string.Empty, default, string.Empty, string.Empty) { } - public MarketFaceEntity(string faceId, int tabId, string key, string summary) + public MarketfaceEntity(string faceId, int tabId, string key, string summary) { - FaceId = faceId; - TabId = tabId; + EmojiId = faceId; + EmojiPackageId = tabId; Key = key; Summary = summary; } @@ -32,13 +32,13 @@ IEnumerable IMessageEntity.PackElement() { new() { - MarketFace = new() + Marketface = new() { Summary = Summary, ItemType = 6, Info = 1, - FaceId = FaceId.UnHex(), - TabId = TabId, + FaceId = EmojiId.UnHex(), + TabId = EmojiPackageId, SubType = 3, Key = Key, // Param = @@ -57,18 +57,18 @@ IEnumerable IMessageEntity.PackElement() IMessageEntity? IMessageEntity.UnpackElement(Elem elem) { - if (elem.MarketFace == null) return null; + if (elem.Marketface == null) return null; - return new MarketFaceEntity( - elem.MarketFace.FaceId.Hex(true), - elem.MarketFace.TabId, - elem.MarketFace.Key, - elem.MarketFace.Summary + return new MarketfaceEntity( + elem.Marketface.FaceId.Hex(true), + elem.Marketface.TabId, + elem.Marketface.Key, + elem.Marketface.Summary ); } public string ToPreviewString() { - return $"[{nameof(MarketFaceEntity)}: TabId: {TabId}; FaceId: {FaceId}; Key: {Key}; Summary: {Summary}]"; + return $"[{nameof(MarketfaceEntity)}: TabId: {EmojiPackageId}; FaceId: {EmojiId}; Key: {Key}; Summary: {Summary}]"; } } \ No newline at end of file diff --git a/Lagrange.Core/Message/Entity/MultiMsgEntity.cs b/Lagrange.Core/Message/Entity/MultiMsgEntity.cs index f860b324e..50ace4c96 100644 --- a/Lagrange.Core/Message/Entity/MultiMsgEntity.cs +++ b/Lagrange.Core/Message/Entity/MultiMsgEntity.cs @@ -197,7 +197,7 @@ public class MultiMessage { [XmlAttribute("serviceID")] public uint ServiceId { get; set; } // 35 - [XmlAttribute("templateID")] public uint TemplateId { get; set; } // 1 + [XmlAttribute("templateID")] public int TemplateId { get; set; } // 1 [XmlAttribute("action")] public string Action { get; set; } = "viewMultiMsg"; diff --git a/Lagrange.Core/Message/MessageBuilder.cs b/Lagrange.Core/Message/MessageBuilder.cs index c6b764137..3a3167b52 100644 --- a/Lagrange.Core/Message/MessageBuilder.cs +++ b/Lagrange.Core/Message/MessageBuilder.cs @@ -242,7 +242,7 @@ public MessageBuilder Keyboard(KeyboardData data) public MessageBuilder MarketFace(string faceId, int tabId, string key, string summary) { - var marketFaceEntity = new MarketFaceEntity(faceId, tabId, key, summary); + var marketFaceEntity = new MarketfaceEntity(faceId, tabId, key, summary); _chain.Add(marketFaceEntity); return this; diff --git a/Lagrange.Core/Message/MessageChain.cs b/Lagrange.Core/Message/MessageChain.cs index ca7009e4d..3ab468f87 100644 --- a/Lagrange.Core/Message/MessageChain.cs +++ b/Lagrange.Core/Message/MessageChain.cs @@ -26,13 +26,13 @@ public sealed class MessageChain : List #region Internal Properties - internal string? SelfUid { get; } + internal string? SelfUid { get; set; } - internal string? Uid { get; } + internal string? Uid { get; set; } - internal bool IsGroup { get; } + internal bool IsGroup { get; set; } - internal List Elements { get; } + internal List Elements { get; set; } #endregion diff --git a/Lagrange.Core/Message/MessagePacker.cs b/Lagrange.Core/Message/MessagePacker.cs index 52c5b0da6..0296b5937 100644 --- a/Lagrange.Core/Message/MessagePacker.cs +++ b/Lagrange.Core/Message/MessagePacker.cs @@ -144,7 +144,7 @@ public static MessageChain Parse(PushMsgBody message, bool isFake = false) switch (message.Body?.RichText?.Ptt) { - case { } groupPtt when chain.IsGroup && groupPtt.FileId == 0: // for legacy ptt + case { } groupPtt when chain.IsGroup && groupPtt.FileId != 0: // for legacy ptt chain.Add(new RecordEntity(groupPtt.GroupFileKey, groupPtt.FileName)); break; case { } privatePtt when !chain.IsGroup: @@ -209,8 +209,8 @@ public static MessageChain ParsePrivateFile(PushMsgBody message) { ResponseHead = new ResponseHead { - FromUid = selfUid, - ToUid = chain.IsGroup ? null : chain.Uid, + FromUid = chain.Uid, + ToUid = chain.IsGroup ? null : selfUid, Grp = !chain.IsGroup ? null : new ResponseGrp // for consistency of code so inverted condition { GroupUin = chain.GroupUin ?? 0, diff --git a/Lagrange.Core/Utility/Crypto/Provider/Ecdh/EcdhProvider.cs b/Lagrange.Core/Utility/Crypto/Provider/Ecdh/EcdhProvider.cs index 2ea9ae0f4..cf771e536 100644 --- a/Lagrange.Core/Utility/Crypto/Provider/Ecdh/EcdhProvider.cs +++ b/Lagrange.Core/Utility/Crypto/Provider/Ecdh/EcdhProvider.cs @@ -59,23 +59,23 @@ public EllipticPoint UnpackPublic(byte[] publicKey) return new EllipticPoint(new BigInteger(x), new BigInteger(y)); } + else + { + var px = new BigInteger(x); + var x3 = px * px * px; + var ax = px * Curve.A; + var right = (x3 + ax + Curve.B) % Curve.P; - var px = new BigInteger(x); - var x3 = px * px * px % Curve.P; - var ax = px * Curve.P; - var right = (x3 + ax + Curve.B) % Curve.P; + var tmp = (Curve.P + 1) >> 2; + var py = BigInteger.ModPow(right, tmp, Curve.P); - var tmp = (Curve.P + 1) >> 2; - var py = BigInteger.ModPow(right, tmp, Curve.P); + if (!(py.IsEven && publicKey[0] == 0x02 || !py.IsEven && publicKey[0] == 0x03)) + { + py = Curve.P - py; + } - if (py.IsEven) - { - tmp = Curve.P; - tmp -= py; - py = tmp; + return new EllipticPoint(px, py); } - - return new EllipticPoint(px, py); } @@ -253,4 +253,4 @@ private static BigInteger Mod(BigInteger a, BigInteger b) if (result < 0) result += b; return result; } -} \ No newline at end of file +} diff --git a/Lagrange.Core/Utility/Extension/ByteExtension.cs b/Lagrange.Core/Utility/Extension/ByteExtension.cs index f25582812..13e699530 100644 --- a/Lagrange.Core/Utility/Extension/ByteExtension.cs +++ b/Lagrange.Core/Utility/Extension/ByteExtension.cs @@ -61,9 +61,7 @@ private static string HexInternal(ReadOnlySpan bytes, bool lower) public static string Md5(this byte[] bytes, bool lower = false) { - using var md5 = MD5.Create(); - var hash = md5.ComputeHash(bytes); - return hash.Hex(lower); + return MD5.HashData(bytes).Hex(lower); } public static async Task Md5Async(this byte[] bytes, bool lower = false) diff --git a/Lagrange.Core/Utility/Extension/StringExtension.cs b/Lagrange.Core/Utility/Extension/StringExtension.cs index 3472943ff..247f4a2b8 100644 --- a/Lagrange.Core/Utility/Extension/StringExtension.cs +++ b/Lagrange.Core/Utility/Extension/StringExtension.cs @@ -8,17 +8,13 @@ public static byte[] UnHex(this string hex) { if (hex.Length % 2 != 0) throw new ArgumentException("Invalid hex string"); - byte[] bytes = new byte[hex.Length / 2]; - for (int i = 0; i < hex.Length; i += 2) bytes[i / 2] = byte.Parse(hex.Substring(i, 2), NumberStyles.HexNumber); - return bytes; + return Convert.FromHexString(hex); } public static byte[] UnHex(this ReadOnlySpan hex) { if (hex.Length % 2 != 0) throw new ArgumentException("Invalid hex string"); - byte[] bytes = new byte[hex.Length / 2]; - for (int i = 0; i < hex.Length; i += 2) bytes[i / 2] = byte.Parse(hex.Slice(i, 2), NumberStyles.HexNumber); - return bytes; + return Convert.FromHexString(hex); } } \ No newline at end of file diff --git a/Lagrange.Core/Utility/Generator/ByteGen.cs b/Lagrange.Core/Utility/Generator/ByteGen.cs index a833238c6..5caa723f0 100644 --- a/Lagrange.Core/Utility/Generator/ByteGen.cs +++ b/Lagrange.Core/Utility/Generator/ByteGen.cs @@ -8,7 +8,7 @@ internal static class ByteGen public static byte[] GenRandomBytes(int length) { var bytes = new byte[length]; - for (int i = 0; i < length; ++i) bytes[i] = (byte) Random.Shared.Next(0, 256); + Random.Shared.NextBytes(bytes); return bytes; } } \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Entity/Action/OneBotCreateFolder.cs b/Lagrange.OneBot/Core/Entity/Action/OneBotCreateFolder.cs new file mode 100644 index 000000000..09d6f380d --- /dev/null +++ b/Lagrange.OneBot/Core/Entity/Action/OneBotCreateFolder.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Lagrange.OneBot.Core.Entity.Action; + +[Serializable] +public class OneBotCreateFolder +{ + [JsonPropertyName("group_id")] public uint GroupId { get; set; } + + [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + + [JsonPropertyName("parent_id")] public string ParentId { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Entity/Action/OneBotSendPacket.cs b/Lagrange.OneBot/Core/Entity/Action/OneBotSendPacket.cs new file mode 100644 index 000000000..6928dcddb --- /dev/null +++ b/Lagrange.OneBot/Core/Entity/Action/OneBotSendPacket.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Lagrange.OneBot.Core.Entity.Action; + +[Serializable] +public class OneBotSendPacket +{ + [JsonPropertyName("data")] public string Data { get; set; } = string.Empty; + + [JsonPropertyName("command")] public string Command { get; set; } = string.Empty; + + [JsonPropertyName("sign")] public bool Sign { get; set; } + + [JsonPropertyName("type")] public byte Type { get; set; } = 12; +} \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Entity/Action/Response/OneBotSendPacketResponse.cs b/Lagrange.OneBot/Core/Entity/Action/Response/OneBotSendPacketResponse.cs new file mode 100644 index 000000000..3f3284686 --- /dev/null +++ b/Lagrange.OneBot/Core/Entity/Action/Response/OneBotSendPacketResponse.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Lagrange.OneBot.Core.Entity.Action.Response; + +[Serializable] +public class OneBotSendPacketResponse +{ + [JsonPropertyName("sequence")] public int Sequence { get; set; } + + [JsonPropertyName("result")] public string Result { get; set; } = string.Empty; + + [JsonPropertyName("retcode")] public int RetCode { get; set; } + + [JsonPropertyName("extra")] public string? Extra { get; set; } +} \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Network/Service/HttpPostService.cs b/Lagrange.OneBot/Core/Network/Service/HttpPostService.cs index 85d4e5d88..0068bdeca 100644 --- a/Lagrange.OneBot/Core/Network/Service/HttpPostService.cs +++ b/Lagrange.OneBot/Core/Network/Service/HttpPostService.cs @@ -42,7 +42,7 @@ public async ValueTask SendJsonAsync(T payload, string? identifier, Cancellat if (payload is OneBotResult) return; // ignore api result var json = JsonSerializer.Serialize(payload); - Log.LogSendingData(_logger, Tag, json); + Log.LogSendingData(_logger, Tag, _url.ToString(), json); using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Headers = { { "X-Self-ID", context.BotUin.ToString() } }, @@ -60,7 +60,7 @@ public async ValueTask SendJsonAsync(T payload, string? identifier, Cancellat } catch (HttpRequestException ex) { - Log.LogPostFailed(_logger, ex, Tag); + Log.LogPostFailed(_logger, ex, Tag, _url.ToString()); } } @@ -122,13 +122,21 @@ private async Task HeartbeatLoop(CancellationToken token) private static partial class Log { - [LoggerMessage(EventId = 1, Level = LogLevel.Trace, Message = "[{tag}] Send: {data}")] - public static partial void LogSendingData(ILogger logger, string tag, string data); + private enum EventIds + { + SendingData = 1, + + PostFailed = 1001, + InvalidUrl + } + + [LoggerMessage(EventId = (int)EventIds.SendingData, Level = LogLevel.Trace, Message = "[{tag}] Send to {url}: {data}")] + public static partial void LogSendingData(ILogger logger, string tag, string url, string data); - [LoggerMessage(EventId = 5, Level = LogLevel.Error, Message = "[{tag}] Post failed")] - public static partial void LogPostFailed(ILogger logger, Exception ex, string tag); + [LoggerMessage(EventId = (int)EventIds.PostFailed, Level = LogLevel.Error, Message = "[{tag}] Post to {url} failed")] + public static partial void LogPostFailed(ILogger logger, Exception ex, string tag, string url); - [LoggerMessage(EventId = 10, Level = LogLevel.Error, Message = "[{tag}] Invalid configuration was detected, url: {url}")] + [LoggerMessage(EventId = (int)EventIds.InvalidUrl, Level = LogLevel.Error, Message = "[{tag}] Invalid configuration was detected, url: {url}")] public static partial void LogInvalidUrl(ILogger logger, string tag, string url); } } diff --git a/Lagrange.OneBot/Core/Network/Service/HttpService.cs b/Lagrange.OneBot/Core/Network/Service/HttpService.cs index d3eb90b08..72241db1a 100644 --- a/Lagrange.OneBot/Core/Network/Service/HttpService.cs +++ b/Lagrange.OneBot/Core/Network/Service/HttpService.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; using System.Net; -using System.Net.NetworkInformation; +using System.Net.Http.Headers; using System.Text.Json; using Lagrange.OneBot.Core.Network.Options; using Microsoft.Extensions.Hosting; @@ -81,9 +81,12 @@ private async Task HandleRequestAsync(HttpListenerContext context, CancellationT var response = context.Response; // no using cause we might need to use it in SendJsonAsync var query = request.QueryString; // avoid creating a new nvc every get + try { string identifier = Guid.NewGuid().ToString(); + Log.LogRequest(_logger, identifier, request.RemoteEndPoint.ToString()); + if (!string.IsNullOrEmpty(_options.AccessToken)) { var authorization = request.Headers.Get("Authorization") ?? @@ -120,35 +123,55 @@ private async Task HandleRequestAsync(HttpListenerContext context, CancellationT payload = JsonSerializer.Serialize(new { action, @params }); break; } - case "POST" when request.ContentType == "application/json": - { - using var reader = new StreamReader(request.InputStream); - var body = await reader.ReadToEndAsync(token); - Log.LogReceived(_logger, identifier, body); - payload = $"{{\"action\":\"{action}\",\"params\":{body}}}"; - break; - } - case "POST" when request.ContentType == "application/x-www-form-urlencoded": + case "POST": { - using var reader = new StreamReader(request.InputStream); - var body = await reader.ReadToEndAsync(token); - Log.LogReceived(_logger, identifier, body); - var @params = body.Split('&') - .Select(pair => pair.Split('=', 2)) - .ToDictionary(pair => pair[0], pair => Uri.UnescapeDataString(pair[1])); - payload = JsonSerializer.Serialize(new { action, @params }); + if (!MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaType)) + { + Log.LogCannotParseMediaType(_logger, request.ContentType ?? string.Empty); + response.StatusCode = (int)HttpStatusCode.UnsupportedMediaType; + response.Close(); + return; + } + + switch (mediaType.MediaType) + { + case "application/json": + { + + using var reader = new StreamReader(request.InputStream); + var body = await reader.ReadToEndAsync(token); + Log.LogReceived(_logger, identifier, body); + payload = $"{{\"action\":\"{action}\",\"params\":{body}}}"; + break; + } + case "application/x-www-form-urlencoded": + { + using var reader = new StreamReader(request.InputStream); + var body = await reader.ReadToEndAsync(token); + Log.LogReceived(_logger, identifier, body); + var @params = body.Split('&') + .Select(pair => pair.Split('=', 2)) + .ToDictionary(pair => pair[0], pair => Uri.UnescapeDataString(pair[1])); + payload = JsonSerializer.Serialize(new { action, @params }); + break; + } + default: + { + Log.LogUnsupportedContentType(_logger, request.ContentType ?? string.Empty); + response.StatusCode = (int)HttpStatusCode.NotAcceptable; // make them happy + response.Close(); + return; + } + } break; } - case "POST": - Log.LogUnsupportedContentType(_logger, request.ContentType ?? string.Empty); - response.StatusCode = (int)HttpStatusCode.NotAcceptable; // make them happy - response.Close(); - return; default: + { Log.LogUnsupportedMethod(_logger, request.HttpMethod); response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; response.Close(); return; + } } Log.LogReceived(_logger, identifier, payload); @@ -162,6 +185,8 @@ private async Task HandleRequestAsync(HttpListenerContext context, CancellationT catch (Exception e) { Log.LogHandleError(_logger, e); + response.StatusCode = (int)HttpStatusCode.InternalServerError; + response.Close(); } } @@ -188,37 +213,59 @@ public async ValueTask SendJsonAsync(T json, string? identifier = null, private static partial class Log { - [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "HttpService started at {prefix}")] + private enum EventIds + { + Started = 1, + Request, + Received, + Send, + + StartFailed = 1001, + CloseFailed, + GetContextError, + HandleError, + AuthFailed, + CannotParseMediaType, + UnsupportedContentType, + UnsupportedMethod, + } + + [LoggerMessage(EventId = (int)EventIds.Started, Level = LogLevel.Information, Message = "HttpService started at {prefix}")] public static partial void LogStarted(ILogger logger, string prefix); - [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "Receive(Conn: {identifier}: {s})")] + [LoggerMessage(EventId = (int)EventIds.Request, Level = LogLevel.Information, Message = "Request(Conn: {identifier} from {ip})")] + public static partial void LogRequest(ILogger logger, string identifier, string ip); + + [LoggerMessage(EventId = (int)EventIds.Received, Level = LogLevel.Information, Message = "Receive(Conn: {identifier}: {s})")] public static partial void LogReceived(ILogger logger, string identifier, string s); - [LoggerMessage(EventId = 2, Level = LogLevel.Trace, Message = "Send(Conn: {identifier}: {s})")] + [LoggerMessage(EventId = (int)EventIds.Send, Level = LogLevel.Trace, Message = "Send(Conn: {identifier}: {s})")] public static partial void LogSend(ILogger logger, string identifier, string s); + [LoggerMessage(EventId = (int)EventIds.CannotParseMediaType, Level = LogLevel.Warning, Message = "Cannot parse media type: {mediaType}")] + public static partial void LogCannotParseMediaType(ILogger logger, string mediaType); - [LoggerMessage(EventId = 993, Level = LogLevel.Warning, Message = "Conn: {identifier} auth failed")] + [LoggerMessage(EventId = (int)EventIds.AuthFailed, Level = LogLevel.Warning, Message = "Conn: {identifier} auth failed")] public static partial void LogAuthFailed(ILogger logger, string identifier); - [LoggerMessage(EventId = 994, Level = LogLevel.Warning, Message = "Unsupported content type: {contentType}")] + [LoggerMessage(EventId = (int)EventIds.UnsupportedContentType, Level = LogLevel.Warning, Message = "Unsupported content type: {contentType}")] public static partial void LogUnsupportedContentType(ILogger logger, string contentType); - [LoggerMessage(EventId = 995, Level = LogLevel.Warning, Message = "Unsupported method: {method}")] + [LoggerMessage(EventId = (int)EventIds.UnsupportedMethod, Level = LogLevel.Warning, Message = "Unsupported method: {method}")] public static partial void LogUnsupportedMethod(ILogger logger, string method); - [LoggerMessage(EventId = 996, Level = LogLevel.Warning, + [LoggerMessage(EventId = (int)EventIds.HandleError, Level = LogLevel.Warning, Message = "An error occurred while handling the request")] public static partial void LogHandleError(ILogger logger, Exception e); - [LoggerMessage(EventId = 997, Level = LogLevel.Warning, + [LoggerMessage(EventId = (int)EventIds.GetContextError, Level = LogLevel.Warning, Message = "An error occurred while getting the context")] public static partial void LogGetContextError(ILogger logger, Exception e); - [LoggerMessage(EventId = 998, Level = LogLevel.Warning, Message = "Failed to gracefully close the listener")] + [LoggerMessage(EventId = (int)EventIds.CloseFailed, Level = LogLevel.Warning, Message = "Failed to gracefully close the listener")] public static partial void LogCloseFailed(ILogger logger, Exception e); - [LoggerMessage(EventId = 999, Level = LogLevel.Error, + [LoggerMessage(EventId = (int)EventIds.StartFailed, Level = LogLevel.Error, Message = "An error occurred while starting the listener")] public static partial void LogStartFailed(ILogger logger, Exception e); } diff --git a/Lagrange.OneBot/Core/Network/Service/ReverseWSService.cs b/Lagrange.OneBot/Core/Network/Service/ReverseWSService.cs index 26ab4dac4..6a4985436 100644 --- a/Lagrange.OneBot/Core/Network/Service/ReverseWSService.cs +++ b/Lagrange.OneBot/Core/Network/Service/ReverseWSService.cs @@ -17,6 +17,9 @@ public partial class ReverseWSService(IOptionsSnapshot { private const string Tag = nameof(ReverseWSService); + private string _urlStr = string.Empty; + private readonly string _identifier = Guid.NewGuid().ToString(); + public event EventHandler? OnMessageReceived; private readonly ReverseWSServiceOptions _options = options.Value; @@ -75,7 +78,7 @@ protected async ValueTask SendJsonAsync(ClientWebSocket ws, T payload, Cancel { var json = JsonSerializer.Serialize(payload); var buffer = Encoding.UTF8.GetBytes(json); - Log.LogSendingData(_logger, Tag, json); + Log.LogSendingData(_logger, Tag, _identifier, json); await semaphore.WaitAsync(token); try { @@ -100,17 +103,17 @@ protected ClientWebSocket CreateDefaultWebSocket(string role) protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - string urlstr = $"{_options.Host}:{_options.Port}{_options.Suffix}"; + _urlStr = $"{_options.Host}:{_options.Port}{_options.Suffix}"; if (!_options.Host.StartsWith("ws://") && !_options.Host.StartsWith("wss://")) { - urlstr = "ws://" + urlstr; + _urlStr = "ws://" + _urlStr; } - string apiurlstr = $"{urlstr}{_options.ApiSuffix}"; - string eventurlstr = $"{urlstr}{_options.EventSuffix}"; + string apiurlstr = $"{_urlStr}{_options.ApiSuffix}"; + string eventurlstr = $"{_urlStr}{_options.EventSuffix}"; - if (!Uri.TryCreate(urlstr, UriKind.Absolute, out var url)) + if (!Uri.TryCreate(_urlStr, UriKind.Absolute, out var url)) { - Log.LogInvalidUrl(_logger, Tag, urlstr); + Log.LogInvalidUrl(_logger, Tag, _urlStr); return; } if (!Uri.TryCreate(apiurlstr, UriKind.Absolute, out var apiUrl)) @@ -136,6 +139,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) ConnCtx = connCtx; await connTask; + Log.LogConnected(_logger, Tag, _identifier, _urlStr); var lifecycle = new OneBotLifecycle(context.BotUin, "connect"); await SendJsonAsync(ws, lifecycle, stoppingToken); @@ -185,13 +189,13 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) } catch (WebSocketException e) when (e.InnerException is HttpRequestException) { - Log.LogClientReconnect(_logger, Tag, _options.ReconnectInterval); + Log.LogClientReconnect(_logger, Tag, _identifier, _options.ReconnectInterval); var interval = TimeSpan.FromMilliseconds(_options.ReconnectInterval); await Task.Delay(interval, stoppingToken); } catch (Exception e) { - Log.LogClientDisconnected(_logger, e, Tag); + Log.LogClientDisconnected(_logger, e, Tag, _identifier); var interval = TimeSpan.FromMilliseconds(_options.ReconnectInterval); await Task.Delay(interval, stoppingToken); } @@ -219,7 +223,7 @@ private async Task ReceiveLoop(ClientWebSocket ws, CancellationToken token) if (received == buffer.Length) Array.Resize(ref buffer, received << 1); } string text = Encoding.UTF8.GetString(buffer, 0, received); - Log.LogDataReceived(_logger, Tag, text); + Log.LogDataReceived(_logger, Tag, _identifier, text); OnMessageReceived?.Invoke(this, new MsgRecvEventArgs(text)); // Handle user handlers error? } } @@ -250,10 +254,24 @@ private async Task HeartbeatLoop(ClientWebSocket ws, CancellationToken token) private static partial class Log { - [LoggerMessage(EventId = 1, Level = LogLevel.Trace, Message = "[{tag}] Send: {data}")] - public static partial void LogSendingData(ILogger logger, string tag, string data); + private enum EventIds + { + Connected = 1, + SendingData, + DataReceived, + + ClientDisconnected = 1001, + ClientReconnect, + InvalidUrl + } - public static void LogDataReceived(ILogger logger, string tag, string data) + [LoggerMessage(EventId = (int)EventIds.Connected, Level = LogLevel.Trace, Message = "[{tag}] Connect({identifier}): {url}")] + public static partial void LogConnected(ILogger logger, string tag, string identifier, string url); + + [LoggerMessage(EventId = (int)EventIds.SendingData, Level = LogLevel.Trace, Message = "[{tag}] Send({identifier}): {data}")] + public static partial void LogSendingData(ILogger logger, string tag, string identifier, string data); + + public static void LogDataReceived(ILogger logger, string tag, string identifier, string data) { if (logger.IsEnabled(LogLevel.Trace)) { @@ -261,23 +279,20 @@ public static void LogDataReceived(ILogger logger, string tag, string data) { data = string.Concat(data.AsSpan(0, 1024), "...", (data.Length - 1024).ToString(), "bytes"); } - InternalLogDataReceived(logger, tag, data); + InternalLogDataReceived(logger, tag, identifier, data); } } - [LoggerMessage(EventId = 2, Level = LogLevel.Trace, Message = "[{tag}] Receive: {data}", SkipEnabledCheck = true)] - private static partial void InternalLogDataReceived(ILogger logger, string tag, string data); - - [LoggerMessage(EventId = 3, Level = LogLevel.Warning, Message = "[{tag}] Client disconnected")] - public static partial void LogClientDisconnected(ILogger logger, Exception e, string tag); + [LoggerMessage(EventId = (int)EventIds.DataReceived, Level = LogLevel.Trace, Message = "[{tag}] Receive({identifier}): {data}", SkipEnabledCheck = true)] + private static partial void InternalLogDataReceived(ILogger logger, string tag, string identifier, string data); - [LoggerMessage(EventId = 4, Level = LogLevel.Information, Message = "[{tag}] Client reconnecting at interval of {interval}")] - public static partial void LogClientReconnect(ILogger logger, string tag, uint interval); + [LoggerMessage(EventId = (int)EventIds.ClientDisconnected, Level = LogLevel.Warning, Message = "[{tag}] Disconnect({identifier})")] + public static partial void LogClientDisconnected(ILogger logger, Exception e, string tag, string identifier); - [LoggerMessage(EventId = 5, Level = LogLevel.Error, Message = "[{tag}] Client connect failed, reconnect after {interval} millisecond")] - public static partial void LogConnectFailed(ILogger logger, string tag, uint interval); + [LoggerMessage(EventId = (int)EventIds.ClientReconnect, Level = LogLevel.Information, Message = "[{tag}] Reconnecting {identifier} at interval of {interval}")] + public static partial void LogClientReconnect(ILogger logger, string tag, string identifier, uint interval); - [LoggerMessage(EventId = 10, Level = LogLevel.Error, Message = "[{tag}] Invalid configuration was detected, url: {url}")] + [LoggerMessage(EventId = (int)EventIds.InvalidUrl, Level = LogLevel.Error, Message = "[{tag}] Invalid configuration was detected, url: {url}")] public static partial void LogInvalidUrl(ILogger logger, string tag, string url); } } diff --git a/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs b/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs index b837443c0..39c64bdaa 100644 --- a/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs +++ b/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs @@ -99,6 +99,21 @@ public async Task HandleOperation(BotContext context, JsonNode? pa return new OneBotResult(null, 0, "ok"); } + throw new Exception(); + } +} + +[Operation("create_group_file_folder")] +public class CreateGroupFileFolderOperation : IOperation +{ + public async Task HandleOperation(BotContext context, JsonNode? payload) + { + if (payload.Deserialize(SerializerOptions.DefaultOptions) is { } folder) + { + await context.GroupFSCreateFolder(folder.GroupId, folder.Name); + return new OneBotResult(null, 0, "ok"); + } + throw new Exception(); } } \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Operation/Generic/FetchMFaceKeyOperation.cs b/Lagrange.OneBot/Core/Operation/Generic/FetchMFaceKeyOperation.cs new file mode 100644 index 000000000..91e7b7ad1 --- /dev/null +++ b/Lagrange.OneBot/Core/Operation/Generic/FetchMFaceKeyOperation.cs @@ -0,0 +1,25 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using Lagrange.Core; +using Lagrange.Core.Common.Interface.Api; +using Lagrange.OneBot.Core.Entity.Action; + +namespace Lagrange.OneBot.Core.Operation.Generic; + +[Operation("fetch_mface_key")] +internal class FetchMFaceKeyOperation : IOperation +{ + public async Task HandleOperation(BotContext context, JsonNode? payload) + { + if (payload?["emoji_ids"]?.Deserialize() is { Length: not 0 } emojiIds) + { + if (await context.FetchMarketFaceKey([.. emojiIds]) is { Count: not 0 } keys) + { + return new(keys, 0, "ok"); + } + else return new(Array.Empty(), -1, "failed"); + } + else throw new Exception(); + } +} diff --git a/Lagrange.OneBot/Core/Operation/Generic/SendPacketOperation.cs b/Lagrange.OneBot/Core/Operation/Generic/SendPacketOperation.cs new file mode 100644 index 000000000..0aaf52d2b --- /dev/null +++ b/Lagrange.OneBot/Core/Operation/Generic/SendPacketOperation.cs @@ -0,0 +1,34 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Lagrange.Core; +using Lagrange.Core.Internal.Packets; +using Lagrange.Core.Utility.Binary; +using Lagrange.Core.Utility.Extension; +using Lagrange.OneBot.Core.Entity.Action; +using Lagrange.OneBot.Core.Entity.Action.Response; + +namespace Lagrange.OneBot.Core.Operation.Generic; + +[Operation(".send_packet")] +public class SendPacketOperation : IOperation +{ + public async Task HandleOperation(BotContext context, JsonNode? payload) + { + if (payload.Deserialize() is { } send) + { + int sequence = context.ContextCollection.Service.GetNewSequence(); + var ssoPacket = new SsoPacket(send.Type, send.Command, (uint)sequence, new BinaryPacket(send.Data.UnHex())); + var task = await context.ContextCollection.Packet.SendPacket(ssoPacket); + + return new OneBotResult(new OneBotSendPacketResponse + { + Sequence = sequence, + RetCode = task.RetCode, + Extra = task.Extra, + Result = task.Payload.ReadBytes(Prefix.Uint32 | Prefix.WithPrefix).Hex() + }, 0, "ok"); + } + + throw new Exception(); + } +} \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Operation/Info/GetGroupInfoOperation.cs b/Lagrange.OneBot/Core/Operation/Info/GetGroupInfoOperation.cs index 9810c19c0..f263e11b6 100644 --- a/Lagrange.OneBot/Core/Operation/Info/GetGroupInfoOperation.cs +++ b/Lagrange.OneBot/Core/Operation/Info/GetGroupInfoOperation.cs @@ -17,8 +17,13 @@ public async Task HandleOperation(BotContext context, JsonNode? pa { var result = (await context.FetchGroups(message.NoCache)).FirstOrDefault(x => x.GroupUin == message.GroupId); - return result == null - ? new OneBotResult(null, -1, "failed") + if (result == null && !message.NoCache) + { + result = (await context.FetchGroups(true)).FirstOrDefault(x => x.GroupUin == message.GroupId); + } + + return result == null + ? new OneBotResult(null, -1, "failed") : new OneBotResult(new OneBotGroup(result), 0, "ok"); } diff --git a/Lagrange.OneBot/LagrangeApp.cs b/Lagrange.OneBot/LagrangeApp.cs index 39337c3f6..04a3a5705 100644 --- a/Lagrange.OneBot/LagrangeApp.cs +++ b/Lagrange.OneBot/LagrangeApp.cs @@ -37,14 +37,19 @@ public class LagrangeApp : IHost public MessageService MessageService { get; set; } public OperationService OperationService { get; set; } + + private bool _isFirstLogin; internal LagrangeApp(IHost host) { _hostApp = host; Logger = Services.GetRequiredService>(); + Services.GetRequiredService(); MessageService = Services.GetRequiredService(); OperationService = Services.GetRequiredService(); + + _isFirstLogin = true; } public async Task StartAsync(CancellationToken cancellationToken = new()) @@ -69,7 +74,7 @@ internal LagrangeApp(IHost host) Instance.Invoker.OnBotOnlineEvent += async (_, args) => { - if (args.Reason == BotOnlineEvent.OnlineReason.Reconnect) return; + if (args.Reason == BotOnlineEvent.OnlineReason.Reconnect && !_isFirstLogin) return; var keystore = Instance.UpdateKeystore(); Logger.LogInformation($"Bot Online: {keystore.Uin}"); @@ -80,6 +85,8 @@ internal LagrangeApp(IHost host) Services.GetRequiredService().RegisterEvents(); await File.WriteAllTextAsync(Configuration["ConfigPath:Keystore"] ?? "keystore.json", json, cancellationToken); + + _isFirstLogin = false; }; if (string.IsNullOrEmpty(Configuration["Account:Password"]) && diff --git a/Lagrange.OneBot/LagrangeAppBuilder.cs b/Lagrange.OneBot/LagrangeAppBuilder.cs index 3d386bae6..4e4dbb12b 100644 --- a/Lagrange.OneBot/LagrangeAppBuilder.cs +++ b/Lagrange.OneBot/LagrangeAppBuilder.cs @@ -46,7 +46,8 @@ public LagrangeAppBuilder ConfigureBots() Protocol = isSuccess ? protocol : Protocols.Linux, AutoReconnect = bool.Parse(Configuration["Account:AutoReconnect"] ?? "true"), UseIPv6Network = bool.Parse(Configuration["Account:UseIPv6Network"] ?? "false"), - GetOptimumServer = bool.Parse(Configuration["Account:GetOptimumServer"] ?? "true") + GetOptimumServer = bool.Parse(Configuration["Account:GetOptimumServer"] ?? "true"), + AutoReLogin = bool.Parse(Configuration["Account:AutoReLogin"] ?? "true"), }; BotKeystore keystore; @@ -117,6 +118,7 @@ public LagrangeAppBuilder ConfigureOneBot() }); Services.AddSingleton(); + Services.AddSingleton(); Services.AddSingleton(); Services.AddSingleton(); Services.AddSingleton(); diff --git a/Lagrange.OneBot/Message/Entity/MarketFaceSegment.cs b/Lagrange.OneBot/Message/Entity/MfaceSegment.cs similarity index 53% rename from Lagrange.OneBot/Message/Entity/MarketFaceSegment.cs rename to Lagrange.OneBot/Message/Entity/MfaceSegment.cs index 64d727947..ca532cd6c 100644 --- a/Lagrange.OneBot/Message/Entity/MarketFaceSegment.cs +++ b/Lagrange.OneBot/Message/Entity/MfaceSegment.cs @@ -7,37 +7,39 @@ namespace Lagrange.OneBot.Message.Entity; [Serializable] -public partial class MarketFaceSegment(string faceId, int tabId, string key, string? summary) +public partial class MfaceSegment(string? url, string emojiId, int emojiPackageId, string key, string? summary) { - [JsonPropertyName("face_id")] public string FaceId { get; set; } = faceId; + [JsonPropertyName("url")] public string? Url { get; set; } = url; - [JsonPropertyName("tab_id")] public int TabId { get; set; } = tabId; + [JsonPropertyName("emoji_package_id")] public int EmojiPackageId { get; set; } = emojiPackageId; + + [JsonPropertyName("emoji_id")] public string EmojiId { get; set; } = emojiId; [JsonPropertyName("key")] public string Key { get; set; } = key; [JsonPropertyName("summary")] public string? Summary { get; set; } = summary; - public MarketFaceSegment() : this(string.Empty, default, string.Empty, null) { } + public MfaceSegment() : this(null, string.Empty, default, string.Empty, null) { } } -[SegmentSubscriber(typeof(MarketFaceEntity), "marketface")] -public partial class MarketFaceSegment : SegmentBase +[SegmentSubscriber(typeof(MarketfaceEntity), "mface")] +public partial class MfaceSegment : SegmentBase { public override void Build(MessageBuilder builder, SegmentBase segment) { - if (segment is not MarketFaceSegment mfs) return; + if (segment is not MfaceSegment mfs) return; if (mfs.Summary == null) { JsonElement tabJson = JsonDocument.Parse( Http.GetAsync( - $"https://i.gtimg.cn/club/item/parcel/{mfs.TabId % 10}/{mfs.TabId}.json" + $"https://i.gtimg.cn/club/item/parcel/{mfs.EmojiPackageId % 10}/{mfs.EmojiPackageId}.json" ).Result ).RootElement; foreach (JsonElement imgJson in tabJson.GetProperty("imgs").EnumerateArray()) { - if (imgJson.GetProperty("id").GetString() == mfs.FaceId) + if (imgJson.GetProperty("id").GetString() == mfs.EmojiId) { mfs.Summary = $"[{imgJson.GetProperty("name").GetString()}]" ?? "[\u5546\u57ce\u8868\u60c5]"; break; @@ -47,13 +49,13 @@ public override void Build(MessageBuilder builder, SegmentBase segment) mfs.Summary ??= "[\u5546\u57ce\u8868\u60c5]"; } - builder.Add(new MarketFaceEntity(mfs.FaceId, mfs.TabId, mfs.Key, mfs.Summary)); + builder.Add(new MarketfaceEntity(mfs.EmojiId, mfs.EmojiPackageId, mfs.Key, mfs.Summary)); } public override SegmentBase FromEntity(MessageChain chain, IMessageEntity entity) { - if (entity is not MarketFaceEntity mfe) throw new ArgumentException("Invalid entity type."); + if (entity is not MarketfaceEntity mfe) throw new ArgumentException("Invalid entity type."); - return new MarketFaceSegment(mfe.FaceId, mfe.TabId, mfe.Key, mfe.Summary); + return new MfaceSegment($"https://gxh.vip.qq.com/club/item/parcel/item/{mfe.EmojiId[..2]}/{mfe.EmojiId}/raw300.gif", mfe.EmojiId, mfe.EmojiPackageId, mfe.Key, mfe.Summary); } } \ No newline at end of file diff --git a/Lagrange.OneBot/Message/Entity/MusicSegment.cs b/Lagrange.OneBot/Message/Entity/MusicSegment.cs new file mode 100644 index 000000000..94d2d7740 --- /dev/null +++ b/Lagrange.OneBot/Message/Entity/MusicSegment.cs @@ -0,0 +1,45 @@ +using Lagrange.Core.Message.Entity; +using Lagrange.Core.Message; +using System.Text.Json.Serialization; +using Lagrange.Core.Utility.Network; +using Lagrange.OneBot.Utility; + +namespace Lagrange.OneBot.Message.Entity; + +[Serializable] +public partial class MusicSegment(string type, string url, string audio, string title, string image, string content) +{ + public MusicSegment() : this("", "", "", "", "", "") { } + + [JsonPropertyName("type")][CQProperty] public string Type { get; set; } = type; + + [JsonPropertyName("url")][CQProperty] public string Url { get; set; } = url; + + [JsonPropertyName("audio")][CQProperty] public string Audio { get; set; } = audio; + + [JsonPropertyName("title")][CQProperty] public string Title { get; set; } = title; + + [JsonPropertyName("content")][CQProperty] public string Content { get; set; } = content; + + [JsonPropertyName("image")][CQProperty] public string Image { get; set; } = image; +} + +[SegmentSubscriber(typeof(ImageEntity), "music")] +public partial class MusicSegment : SegmentBase +{ + public override void Build(MessageBuilder builder, SegmentBase segment) + { + if (segment is MusicSegment musicSegment) + { + var content = MusicSigner.Sign(musicSegment); + if (string.IsNullOrEmpty(content)) + throw new ArgumentNullException("music SignerServer response Errors!"); + builder.LightApp(content); + } + } + + public override SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity) + { + return null; + } +} diff --git a/Lagrange.OneBot/Resources/appsettings.json b/Lagrange.OneBot/Resources/appsettings.json index fc6b76c22..b53263b11 100644 --- a/Lagrange.OneBot/Resources/appsettings.json +++ b/Lagrange.OneBot/Resources/appsettings.json @@ -7,6 +7,7 @@ } }, "SignServerUrl": "https://sign.lagrangecore.org/api/sign", + "MusicSignServerUrl": "", "Account": { "Uin": 0, "Password": "", @@ -15,8 +16,8 @@ "GetOptimumServer": true }, "Message": { - "IgnoreSelf": true, - "StringPost": false + "IgnoreSelf": true, + "StringPost": false }, "QrCode": { "ConsoleCompatibilityMode": false diff --git a/Lagrange.OneBot/Utility/MusicSigner.cs b/Lagrange.OneBot/Utility/MusicSigner.cs new file mode 100644 index 000000000..e7370187b --- /dev/null +++ b/Lagrange.OneBot/Utility/MusicSigner.cs @@ -0,0 +1,54 @@ +using Lagrange.OneBot.Message.Entity; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System.Net.Http.Json; +using System.Text.Json.Nodes; + +namespace Lagrange.OneBot.Utility; + +public class MusicSigner +{ + private static string? _signServer; + + private readonly static HttpClient _client = new(); + + public MusicSigner(IConfiguration config, ILogger logger) + { + _signServer = config["MusicSignServerUrl"] ?? ""; + + if (string.IsNullOrEmpty(_signServer)) + { + logger.LogWarning("MusicSignServer is not available, sign may be failed"); + } + else + { + logger.LogInformation("MusicSignServer Service is successfully established"); + } + } + + + public static string? Sign(MusicSegment musicSegment) + { + if (string.IsNullOrEmpty(_signServer)) return null; + + var payload = new JsonObject() + { + { "type" , musicSegment.Type }, + { "url" , musicSegment.Url }, + { "audio" , musicSegment.Audio }, + { "title" , musicSegment.Title }, + { "image" , musicSegment.Image }, + { "singer" , musicSegment.Content }, + }; + + try + { + var message = _client.PostAsJsonAsync(_signServer, payload).Result; + return message.Content.ReadAsStringAsync().Result; + } + catch + { + return null; + } + } +}