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;
+ }
+ }
+}