From ad2c4a5207d3f17214c6e83eefc45dcc17b66277 Mon Sep 17 00:00:00 2001
From: CwkDark <177549718+CwkDark@users.noreply.github.com>
Date: Fri, 23 Aug 2024 20:06:04 +0800
Subject: [PATCH 01/11] add git version info (#542)

---
 Lagrange.OneBot/Program.cs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Lagrange.OneBot/Program.cs b/Lagrange.OneBot/Program.cs
index 5152070a9..bb47932f6 100644
--- a/Lagrange.OneBot/Program.cs
+++ b/Lagrange.OneBot/Program.cs
@@ -9,6 +9,11 @@ internal abstract class Program
 {
     public static void Main(string[] args)
     {
+        string version = Assembly.GetAssembly(typeof(Program))?
+            .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
+            .InformationalVersion ?? "Unknown Lagrange.OneBot Version";
+        Console.WriteLine($"Lagrange.OneBot Version: {version}\n");
+
         Console.OutputEncoding = Encoding.UTF8;
         Console.InputEncoding = Encoding.UTF8;
 

From 207e0197359a0d63395fd77791a25e73f535d926 Mon Sep 17 00:00:00 2001
From: dogdie233 <dogdie233@163.com>
Date: Fri, 23 Aug 2024 20:06:59 +0800
Subject: [PATCH 02/11] =?UTF-8?q?[Core]=20Group=20clock=20in=20=E7=BE=A4?=
 =?UTF-8?q?=E6=89=93=E5=8D=A1=20(#528)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [Core] Group clock in 群打卡

* OIdb subCmd漏了,补上ww
---
 .../Common/Entity/BotGroupClockInResult.cs    | 52 +++++++++++++++
 .../Common/Interface/Api/OperationExt.cs      |  9 +++
 .../Logic/Implementation/OperationLogic.cs    |  7 +++
 .../Event/Action/GroupClockInEvent.cs         | 24 +++++++
 .../Oidb/Request/OidbSvcTrpcTcp0xEB7_1.cs     | 27 ++++++++
 .../Response/OidbSvcTrpcTcp0xEB7_1Response.cs | 27 ++++++++
 .../Service/Action/GroupClockInService.cs     | 63 +++++++++++++++++++
 7 files changed, 209 insertions(+)
 create mode 100644 Lagrange.Core/Common/Entity/BotGroupClockInResult.cs
 create mode 100644 Lagrange.Core/Internal/Event/Action/GroupClockInEvent.cs
 create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0xEB7_1.cs
 create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0xEB7_1Response.cs
 create mode 100644 Lagrange.Core/Internal/Service/Action/GroupClockInService.cs

diff --git a/Lagrange.Core/Common/Entity/BotGroupClockInResult.cs b/Lagrange.Core/Common/Entity/BotGroupClockInResult.cs
new file mode 100644
index 000000000..1520d32c1
--- /dev/null
+++ b/Lagrange.Core/Common/Entity/BotGroupClockInResult.cs
@@ -0,0 +1,52 @@
+namespace Lagrange.Core.Common.Entity;
+
+[Serializable]
+public class BotGroupClockInResult
+{
+    public BotGroupClockInResult() { }
+
+    public BotGroupClockInResult(bool isSuccess)
+    {
+        IsSuccess = isSuccess;
+    }
+
+    /// <summary>
+    /// Is the clock in successful
+    /// </summary>
+    public bool IsSuccess { get; set; } = false;
+
+    /// <summary>
+    /// Maybe "今日已成功打卡"
+    /// </summary>
+    public string Title { get; set; } = string.Empty;
+
+    /// <summary>
+    /// Maybe "已打卡N天"
+    /// </summary>
+    public string KeepDayText { get; set; } = string.Empty;
+
+    /// <summary>
+    /// Maybe "群内排名第N位"
+    /// </summary>
+    public string GroupRankText { get; set; } = string.Empty;
+
+    /// <summary>
+    /// The utc time of clock in
+    /// </summary>
+    public DateTime ClockInUtcTime { get; set; } = DateTime.UnixEpoch; // 打卡时间
+
+    /// <summary>
+    /// Detail info url
+    /// </summary>
+    public string DetailUrl { get; set; } = string.Empty;  // https://qun.qq.com/v2/signin/detail?...
+
+    public static BotGroupClockInResult Fail() => new BotGroupClockInResult()
+    {
+        IsSuccess = false
+    };
+
+    public static BotGroupClockInResult Success() => new BotGroupClockInResult()
+    {
+        IsSuccess = true
+    };
+}
diff --git a/Lagrange.Core/Common/Interface/Api/OperationExt.cs b/Lagrange.Core/Common/Interface/Api/OperationExt.cs
index 4c37edb19..ef1fb80a7 100644
--- a/Lagrange.Core/Common/Interface/Api/OperationExt.cs
+++ b/Lagrange.Core/Common/Interface/Api/OperationExt.cs
@@ -152,6 +152,15 @@ public static Task<bool> Like(this BotContext bot, uint targetUin, uint count =
         return bot.ContextCollection.Business.OperationLogic.GetRoamMessage(targetChain.FriendUin, timestamp, count);
     }
 
+    /// <summary>
+    /// Do group clock in (群打卡)
+    /// </summary>
+    /// <param name="bot">target BotContext</param>
+    /// <param name="groupUin">target groupUin</param>
+    /// <returns></returns>
+    public static Task<BotGroupClockInResult> ClockInGroup(this BotContext bot, uint groupUin)
+        => bot.ContextCollection.Business.OperationLogic.ClockInGroup(groupUin);
+
     public static Task<BotUserInfo?> FetchUserInfo(this BotContext bot, uint uin, bool refreshCache = false)
         => bot.ContextCollection.Business.OperationLogic.FetchUserInfo(uin, refreshCache);
 
diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
index 54ad40491..ff9aff75a 100644
--- a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
+++ b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
@@ -453,4 +453,11 @@ public async Task<bool> SetNeedToConfirmSwitch(bool enableNoNeed)
         var results = await Collection.Business.SendEvent(fetchMarketFaceKeyEvent);
         return results.Count != 0 ? ((FetchMarketFaceKeyEvent)results[0]).Keys : null;
     }
+
+    public async Task<BotGroupClockInResult> ClockInGroup(uint groupUin)
+    {
+        var groupClockInEvent = GroupClockInEvent.Create(groupUin);
+        var results = await Collection.Business.SendEvent(groupClockInEvent);
+        return ((GroupClockInEvent)results[0]).ResultInfo ?? new BotGroupClockInResult(false);
+    }
 }
\ No newline at end of file
diff --git a/Lagrange.Core/Internal/Event/Action/GroupClockInEvent.cs b/Lagrange.Core/Internal/Event/Action/GroupClockInEvent.cs
new file mode 100644
index 000000000..e7d1c6209
--- /dev/null
+++ b/Lagrange.Core/Internal/Event/Action/GroupClockInEvent.cs
@@ -0,0 +1,24 @@
+using Lagrange.Core.Common.Entity;
+
+namespace Lagrange.Core.Internal.Event.Action;
+
+internal class GroupClockInEvent : ProtocolEvent
+{
+    public uint GroupUin { get; set; }
+    public BotGroupClockInResult? ResultInfo { get; set; }
+
+    private GroupClockInEvent(uint groupUin) : base(true)
+    {
+        GroupUin = groupUin;
+        ResultInfo = null;
+    }
+
+    private GroupClockInEvent(int resultCode, BotGroupClockInResult result) : base(resultCode)
+    {
+        ResultInfo = result;
+    }
+
+    public static GroupClockInEvent Create(uint groupUin) => new(groupUin);
+
+    public static GroupClockInEvent Result(int resultCode, BotGroupClockInResult result) => new(resultCode, result);
+}
diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0xEB7_1.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0xEB7_1.cs
new file mode 100644
index 000000000..007bc21c3
--- /dev/null
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0xEB7_1.cs
@@ -0,0 +1,27 @@
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request;
+
+#pragma warning disable CS8618
+// ReSharper disable InconsistentNaming
+
+/// <summary>
+/// Group Clock In
+/// </summary>
+[ProtoContract]
+[OidbSvcTrpcTcp(0xEB7, 1)]
+internal class OidbSvcTrpcTcp0xEB7_1
+{
+    [ProtoMember(2)] public BodyClass Body { get; set; }
+
+    [ProtoContract]
+    internal class BodyClass
+    {
+        [ProtoMember(1)] public string Uin { get; set; }
+
+        [ProtoMember(2)] public string GroupUin { get; set; }
+
+        // 不确定要不要加,测试过没有这个参数也是可以的
+        [ProtoMember(3)] public string AppVersion { get; set; }
+    }
+}
diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0xEB7_1Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0xEB7_1Response.cs
new file mode 100644
index 000000000..fe4b7648d
--- /dev/null
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0xEB7_1Response.cs
@@ -0,0 +1,27 @@
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response;
+
+#pragma warning disable CS8618
+// ReSharper disable InconsistentNaming
+
+[ProtoContract]
+internal class OidbSvcTrpcTcp0xEB7_1Response
+{
+    [ProtoMember(2)] public BodyClass Body { get; set; }
+
+    [ProtoContract]
+    internal class BodyClass
+    {
+        [ProtoMember(2)] public ResultClass Result { get; set; }
+
+        [ProtoContract]
+        internal class ResultClass
+        {
+            [ProtoMember(1)] public string Title { get; set; }  // 今日已成功打卡
+            [ProtoMember(2)] public string KeepDayText { get; set; }  // 已打卡N天
+            [ProtoMember(3)] public string[] ClockInInfo1 { get; set; }  // ["群内排名第N位", "[clock in timestamp (second)]"]
+            [ProtoMember(4)] public string DetailUrl { get; set; }  // https://qun.qq.com/v2/signin/detail?...
+        }
+    }
+}
diff --git a/Lagrange.Core/Internal/Service/Action/GroupClockInService.cs b/Lagrange.Core/Internal/Service/Action/GroupClockInService.cs
new file mode 100644
index 000000000..fa1d6acd7
--- /dev/null
+++ b/Lagrange.Core/Internal/Service/Action/GroupClockInService.cs
@@ -0,0 +1,63 @@
+using Lagrange.Core.Common;
+using Lagrange.Core.Common.Entity;
+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.Internal.Packets.Service.Oidb.Response;
+using Lagrange.Core.Utility.Extension;
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Service.Action;
+
+[EventSubscribe(typeof(GroupClockInEvent))]
+[Service("OidbSvcTrpcTcp.0xeb7_1")]
+internal class GroupClockInService : BaseService<GroupClockInEvent>
+{
+    protected override bool Build(GroupClockInEvent input, BotKeystore keystore, BotAppInfo appInfo,
+        BotDeviceInfo device, out Span<byte> output, out List<Memory<byte>>? extraPackets)
+    {
+        var packet = new OidbSvcTrpcTcpBase<OidbSvcTrpcTcp0xEB7_1>(new()
+        {
+            Body = new()
+            {
+                Uin = keystore.Uin.ToString(),
+                GroupUin = input.GroupUin.ToString(),
+                AppVersion = appInfo.CurrentVersion
+            }
+        });
+
+        output = packet.Serialize();
+        extraPackets = null;
+        return true;
+    }
+
+    protected override bool Parse(Span<byte> input, BotKeystore keystore, BotAppInfo appInfo,
+        BotDeviceInfo device, out GroupClockInEvent output, out List<ProtocolEvent>? extraEvents)
+    {
+        var payload = Serializer.Deserialize<OidbSvcTrpcTcpBase<OidbSvcTrpcTcp0xEB7_1Response>>(input);
+        extraEvents = null;
+
+        var payloadResult = payload.Body?.Body?.Result;
+        if (payload.ErrorCode != 0 || payloadResult == null)
+        {
+            output = GroupClockInEvent.Result((int)payload.ErrorCode, new BotGroupClockInResult(false));
+            return true;
+        }
+
+        if (payloadResult.ClockInInfo1 is not { Length: > 1 } || !long.TryParse(payloadResult.ClockInInfo1[1], out var clockInTimestamp))
+            clockInTimestamp = 0;
+
+        var result = new BotGroupClockInResult(true)
+        {
+            Title = payloadResult.Title ?? string.Empty,
+            KeepDayText = payloadResult.KeepDayText ?? string.Empty,
+            GroupRankText = payloadResult.ClockInInfo1 is { Length: > 0 } ? payloadResult.ClockInInfo1[0] : string.Empty,
+            ClockInUtcTime = DateTime.UnixEpoch + TimeSpan.FromSeconds(clockInTimestamp),
+            DetailUrl = payloadResult.DetailUrl ?? string.Empty,
+        };
+        output = GroupClockInEvent.Result((int)payload.ErrorCode, result);
+
+        return true;
+    }
+}

From 1a8f1710ad3d205d56e22661c32449c7a2f4eefc Mon Sep 17 00:00:00 2001
From: pk5ls20 <pk5ls20@outlook.com>
Date: Fri, 23 Aug 2024 20:14:37 +0800
Subject: [PATCH 03/11] [All] Add DeleteGroupFolderOp & adjust
 CreateGroupFolderOp (#540)

* [All] Add `DeleteGroupFileFolderOperation` & adjust `CreateGroupFileFolderOperation`
- Implemented `DeleteGroupFileFolderOperation`
- Add error codes and error messages obtained from the server response for both `DeleteGroupFileFolderOperation` and `CreateGroupFileFolderOperation`
- fix Oidb subCmd in `OidbSvcTrpcTcp0x6D7_0.cs`

* chore: use Tuple instead of KeyValuePair, resolve https://github.com/LagrangeDev/Lagrange.Core/pull/540#discussion_r1728863261
---
 .../Common/Interface/Api/GroupExt.cs          |  5 ++-
 .../Logic/Implementation/OperationLogic.cs    | 15 ++++++-
 .../Event/Action/GroupFSCreateFolderEvent.cs  |  9 +++-
 .../Event/Action/GroupFSDeleteFolderEvent.cs  | 25 +++++++++++
 .../Oidb/Request/OidbSvcTrpcTcp0x6D7_0.cs     |  8 ++--
 .../Oidb/Request/OidbSvcTrpcTcp0x6D7_1.cs     | 24 +++++++++++
 .../Response/OidbSvcTrpcTcp0x6D7Response.cs   | 14 +++++++
 .../Response/OidbSvcTrpcTcp0x6D7_0Response.cs | 36 ++++++++++++++++
 .../Response/OidbSvcTrpcTcp0x6D7_1Response.cs | 16 +++++++
 .../Action/GroupFSCreateFolderService.cs      |  7 ++--
 .../Action/GroupFSDeleteFolderService.cs      | 42 +++++++++++++++++++
 .../Core/Entity/Action/OneBotDeleteFolder.cs  | 11 +++++
 .../Core/Operation/File/GroupFSOperations.cs  | 19 ++++++++-
 13 files changed, 217 insertions(+), 14 deletions(-)
 create mode 100644 Lagrange.Core/Internal/Event/Action/GroupFSDeleteFolderEvent.cs
 create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_1.cs
 create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7Response.cs
 create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_0Response.cs
 create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1Response.cs
 create mode 100644 Lagrange.Core/Internal/Service/Action/GroupFSDeleteFolderService.cs
 create mode 100644 Lagrange.OneBot/Core/Entity/Action/OneBotDeleteFolder.cs

diff --git a/Lagrange.Core/Common/Interface/Api/GroupExt.cs b/Lagrange.Core/Common/Interface/Api/GroupExt.cs
index a8e2545bf..91dbca303 100644
--- a/Lagrange.Core/Common/Interface/Api/GroupExt.cs
+++ b/Lagrange.Core/Common/Interface/Api/GroupExt.cs
@@ -107,9 +107,12 @@ public static Task<bool> GroupFSMove(this BotContext bot, uint groupUin, string
     public static Task<bool> GroupFSDelete(this BotContext bot, uint groupUin, string fileId)
         => bot.ContextCollection.Business.OperationLogic.GroupFSDelete(groupUin, fileId);
     
-    public static Task<bool> GroupFSCreateFolder(this BotContext bot, uint groupUin, string name)
+    public static Task<(int, string)> GroupFSCreateFolder(this BotContext bot, uint groupUin, string name)
         => bot.ContextCollection.Business.OperationLogic.GroupFSCreateFolder(groupUin, name);
     
+    public static Task<(int, string)> GroupFSDeleteFolder(this BotContext bot, uint groupUin, string folderId)
+        => bot.ContextCollection.Business.OperationLogic.GroupFSDeleteFolder(groupUin, folderId);
+    
     public static Task<bool> GroupFSUpload(this BotContext bot, uint groupUin, FileEntity fileEntity, string targetDirectory = "/")
         => bot.ContextCollection.Business.OperationLogic.GroupFSUpload(groupUin, fileEntity, targetDirectory);
 
diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
index ff9aff75a..5fa9c786b 100644
--- a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
+++ b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
@@ -164,11 +164,22 @@ public async Task<bool> GroupFSDelete(uint groupUin, string fileId)
         return events.Count != 0 && ((GroupFSDeleteEvent)events[0]).ResultCode == 0;
     }
 
-    public async Task<bool> GroupFSCreateFolder(uint groupUin, string name)
+    public async Task<(int, string)> 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;
+        var retCode = events.Count > 0 ? ((GroupFSCreateFolderEvent)events[0]).ResultCode : -1;
+        var retMsg = events.Count > 0 ? ((GroupFSCreateFolderEvent)events[0]).RetMsg : "";
+        return new(retCode, retMsg);
+    }
+    
+    public async Task<(int, string)> GroupFSDeleteFolder(uint groupUin, string folderId)
+    {
+        var groupFSDeleteFolderEvent = GroupFSDeleteFolderEvent.Create(groupUin, folderId);
+        var events = await Collection.Business.SendEvent(groupFSDeleteFolderEvent);
+        var retCode = events.Count > 0 ? ((GroupFSDeleteFolderEvent)events[0]).ResultCode : -1;
+        var retMsg = events.Count > 0 ? ((GroupFSDeleteFolderEvent)events[0]).RetMsg : "";
+        return new(retCode, retMsg);
     }
 
     public Task<bool> GroupFSUpload(uint groupUin, FileEntity fileEntity, string targetDirectory)
diff --git a/Lagrange.Core/Internal/Event/Action/GroupFSCreateFolderEvent.cs b/Lagrange.Core/Internal/Event/Action/GroupFSCreateFolderEvent.cs
index d0d99bb2a..bda99c710 100644
--- a/Lagrange.Core/Internal/Event/Action/GroupFSCreateFolderEvent.cs
+++ b/Lagrange.Core/Internal/Event/Action/GroupFSCreateFolderEvent.cs
@@ -5,6 +5,8 @@ internal class GroupFSCreateFolderEvent : ProtocolEvent
     public uint GroupUin { get; }
     
     public string Name { get; } = string.Empty;
+    
+    public string RetMsg { get; set; } = string.Empty;
 
     private GroupFSCreateFolderEvent(uint groupUin, string name) : base(true)
     {
@@ -12,9 +14,12 @@ private GroupFSCreateFolderEvent(uint groupUin, string name) : base(true)
         Name = name;
     }
 
-    private GroupFSCreateFolderEvent(int resultCode) : base(resultCode) { }
+    private GroupFSCreateFolderEvent(int resultCode, string retMsg) : base(resultCode)
+    {
+        RetMsg = retMsg;
+    }
     
     public static GroupFSCreateFolderEvent Create(uint groupUin, string name) => new(groupUin, name);
     
-    public static GroupFSCreateFolderEvent Result(int resultCode) => new(resultCode);
+    public static GroupFSCreateFolderEvent Result(int resultCode, string retMsg) => new(resultCode, retMsg);
 }
\ No newline at end of file
diff --git a/Lagrange.Core/Internal/Event/Action/GroupFSDeleteFolderEvent.cs b/Lagrange.Core/Internal/Event/Action/GroupFSDeleteFolderEvent.cs
new file mode 100644
index 000000000..b9c2a130f
--- /dev/null
+++ b/Lagrange.Core/Internal/Event/Action/GroupFSDeleteFolderEvent.cs
@@ -0,0 +1,25 @@
+namespace Lagrange.Core.Internal.Event.Action;
+
+internal class GroupFSDeleteFolderEvent : ProtocolEvent
+{
+    public uint GroupUin { get; }
+    
+    public string FolderId { get; } = string.Empty;
+    
+    public string RetMsg { get; set; } = string.Empty;
+
+    private GroupFSDeleteFolderEvent(uint groupUin, string folderId) : base(true)
+    {
+        GroupUin = groupUin;
+        FolderId = folderId;
+    }
+
+    private GroupFSDeleteFolderEvent(int resultCode, string retMsg) : base(resultCode)
+    {
+        RetMsg = retMsg;
+    }
+    
+    public static GroupFSDeleteFolderEvent Create(uint groupUin, string folderId) => new(groupUin, folderId);
+    
+    public static GroupFSDeleteFolderEvent Result(int resultCode, string retMsg) => new(resultCode, retMsg);
+}
\ 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
index 1a3c299f0..d138e346d 100644
--- a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_0.cs
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_0.cs
@@ -1,4 +1,4 @@
-using ProtoBuf;
+using ProtoBuf;
 
 namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request;
 
@@ -9,14 +9,14 @@ namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request;
 /// Create Folder
 /// </summary>
 [ProtoContract]
-[OidbSvcTrpcTcp(0x6D7, 9)]
+[OidbSvcTrpcTcp(0x6D7, 0)]
 internal class OidbSvcTrpcTcp0x6D7_0
 {
-    [ProtoMember(1)] public OidbSvcTrpcTcp0x6D7_0Folder Folder { get; set; }
+    [ProtoMember(1)] public OidbSvcTrpcTcp0x6D7_0Create Create { get; set; }
 }
 
 [ProtoContract]
-internal class OidbSvcTrpcTcp0x6D7_0Folder
+internal class OidbSvcTrpcTcp0x6D7_0Create
 {
     [ProtoMember(1)] public uint GroupUin { get; set; }
     
diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_1.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_1.cs
new file mode 100644
index 000000000..b518d6027
--- /dev/null
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_1.cs
@@ -0,0 +1,24 @@
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request;
+
+#pragma warning disable CS8618
+// ReSharper disable InconsistentNaming
+
+/// <summary>
+/// Delete Folder
+/// </summary>
+[ProtoContract]
+[OidbSvcTrpcTcp(0x6D7, 1)]
+internal class OidbSvcTrpcTcp0x6D7_1
+{
+    [ProtoMember(2)] public OidbSvcTrpcTcp0x6D7_1Delete Delete { get; set; }
+}
+
+[ProtoContract]
+internal class OidbSvcTrpcTcp0x6D7_1Delete
+{
+    [ProtoMember(1)] public uint GroupUin { get; set; }
+    
+    [ProtoMember(3)] public string FolderId { get; set; }
+}
\ No newline at end of file
diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7Response.cs
new file mode 100644
index 000000000..a898b54fa
--- /dev/null
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7Response.cs
@@ -0,0 +1,14 @@
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response;
+
+#pragma warning disable CS8618
+// ReSharper disable InconsistentNaming
+
+[ProtoContract]
+internal class OidbSvcTrpcTcp0x6D7Response
+{
+    [ProtoMember(1)] public OidbSvcTrpcTcp0x6D7_0Response Create { get; set; }
+    
+    [ProtoMember(2)] public OidbSvcTrpcTcp0x6D7_1Response Delete { get; set; }
+}
diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_0Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_0Response.cs
new file mode 100644
index 000000000..a70f4dbf9
--- /dev/null
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_0Response.cs
@@ -0,0 +1,36 @@
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response;
+
+#pragma warning disable CS8618
+// ReSharper disable InconsistentNaming
+
+[ProtoContract]
+internal class OidbSvcTrpcTcp0x6D7_0Response
+{
+    [ProtoMember(1)] public int Retcode { get; set; }
+    
+    [ProtoMember(2)] public string RetMsg { get; set; }
+    
+    [ProtoMember(3)] public string ClientWording { get; set; }
+    
+    [ProtoMember(4)] public OidbSvcTrpcTcp0x6D7_0ResponseFolderInfo FolderInfo { get; set; }
+}
+
+[ProtoContract]
+internal class OidbSvcTrpcTcp0x6D7_0ResponseFolderInfo
+{
+    [ProtoMember(1)] public string FolderId { get; set; }
+    
+    [ProtoMember(2)] public string FolderPath { get; set; }
+    
+    [ProtoMember(3)] public string FolderName { get; set; }
+    
+    [ProtoMember(4)] public uint Timestamp4 { get; set; }
+    
+    [ProtoMember(5)] public uint Timestamp5 { get; set; }
+    
+    [ProtoMember(6)] public uint OperatorUin6 { get; set; }
+    
+    [ProtoMember(7)] public uint OperatorUin9 { get; set; }
+}
\ No newline at end of file
diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1Response.cs
new file mode 100644
index 000000000..25e9b3757
--- /dev/null
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1Response.cs
@@ -0,0 +1,16 @@
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response;
+
+#pragma warning disable CS8618
+// ReSharper disable InconsistentNaming
+
+[ProtoContract]
+internal class OidbSvcTrpcTcp0x6D7_1Response
+{
+    [ProtoMember(1)] public Int32 Retcode { get; set; }
+    
+    [ProtoMember(2)] public string RetMsg { get; set; }
+    
+    [ProtoMember(3)] public string ClientWording { get; set; }
+}
\ No newline at end of file
diff --git a/Lagrange.Core/Internal/Service/Action/GroupFSCreateFolderService.cs b/Lagrange.Core/Internal/Service/Action/GroupFSCreateFolderService.cs
index bc1872483..da91cabf7 100644
--- a/Lagrange.Core/Internal/Service/Action/GroupFSCreateFolderService.cs
+++ b/Lagrange.Core/Internal/Service/Action/GroupFSCreateFolderService.cs
@@ -3,6 +3,7 @@
 using Lagrange.Core.Internal.Event.Action;
 using Lagrange.Core.Internal.Packets.Service.Oidb;
 using Lagrange.Core.Internal.Packets.Service.Oidb.Request;
+using Lagrange.Core.Internal.Packets.Service.Oidb.Response;
 using Lagrange.Core.Utility.Extension;
 using ProtoBuf;
 
@@ -17,7 +18,7 @@ protected override bool Build(GroupFSCreateFolderEvent input, BotKeystore keysto
     {
         var packet = new OidbSvcTrpcTcpBase<OidbSvcTrpcTcp0x6D7_0>(new OidbSvcTrpcTcp0x6D7_0
         {
-            Folder = new OidbSvcTrpcTcp0x6D7_0Folder
+            Create = new OidbSvcTrpcTcp0x6D7_0Create
             {
                 GroupUin = input.GroupUin,
                 RootDirectory = "/",
@@ -33,9 +34,9 @@ protected override bool Build(GroupFSCreateFolderEvent input, BotKeystore keysto
     protected override bool Parse(Span<byte> input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device,
         out GroupFSCreateFolderEvent output, out List<ProtocolEvent>? extraEvents)
     {
-        var packet = Serializer.Deserialize<OidbSvcTrpcTcpBase<byte[]>>(input);
+        var packet = Serializer.Deserialize<OidbSvcTrpcTcpBase<OidbSvcTrpcTcp0x6D7Response>>(input);
         
-        output = GroupFSCreateFolderEvent.Result((int)packet.ErrorCode);
+        output = GroupFSCreateFolderEvent.Result(packet.Body.Create.Retcode, packet.Body.Create.RetMsg);
         extraEvents = null;
         return true;
     }
diff --git a/Lagrange.Core/Internal/Service/Action/GroupFSDeleteFolderService.cs b/Lagrange.Core/Internal/Service/Action/GroupFSDeleteFolderService.cs
new file mode 100644
index 000000000..00763fa09
--- /dev/null
+++ b/Lagrange.Core/Internal/Service/Action/GroupFSDeleteFolderService.cs
@@ -0,0 +1,42 @@
+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.Internal.Packets.Service.Oidb.Response;
+using Lagrange.Core.Utility.Extension;
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Service.Action;
+
+[EventSubscribe(typeof(GroupFSDeleteFolderEvent))]
+[Service("OidbSvcTrpcTcp.0x6d7_1")]
+internal class GroupFSDeleteFolderService : BaseService<GroupFSDeleteFolderEvent>
+{
+    protected override bool Build(GroupFSDeleteFolderEvent input, BotKeystore keystore, BotAppInfo appInfo,
+        BotDeviceInfo device, out Span<byte> output, out List<Memory<byte>>? extraPackets)
+    {
+        var packet = new OidbSvcTrpcTcpBase<OidbSvcTrpcTcp0x6D7_1>(new OidbSvcTrpcTcp0x6D7_1
+        {
+            Delete = new OidbSvcTrpcTcp0x6D7_1Delete
+            {
+                GroupUin = input.GroupUin,
+                FolderId = input.FolderId
+            }
+        }, false, true);
+        
+        output = packet.Serialize();
+        extraPackets = null;
+        return true;
+    }
+
+    protected override bool Parse(Span<byte> input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device,
+        out GroupFSDeleteFolderEvent output, out List<ProtocolEvent>? extraEvents)
+    {
+        var packet = Serializer.Deserialize<OidbSvcTrpcTcpBase<OidbSvcTrpcTcp0x6D7Response>>(input);
+        
+        output = GroupFSDeleteFolderEvent.Result(packet.Body.Delete.Retcode, packet.Body.Delete.RetMsg);
+        extraEvents = null;
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/Lagrange.OneBot/Core/Entity/Action/OneBotDeleteFolder.cs b/Lagrange.OneBot/Core/Entity/Action/OneBotDeleteFolder.cs
new file mode 100644
index 000000000..c53335aff
--- /dev/null
+++ b/Lagrange.OneBot/Core/Entity/Action/OneBotDeleteFolder.cs
@@ -0,0 +1,11 @@
+using System.Text.Json.Serialization;
+
+namespace Lagrange.OneBot.Core.Entity.Action;
+
+[Serializable]
+public class OneBotDeleteFolder
+{
+    [JsonPropertyName("group_id")] public uint GroupId { get; set; }
+    
+    [JsonPropertyName("folder_id")] public string FolderId { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs b/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs
index 39c64bdaa..f88cdff4f 100644
--- a/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs
+++ b/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs
@@ -110,8 +110,23 @@ public async Task<OneBotResult> HandleOperation(BotContext context, JsonNode? pa
     {
         if (payload.Deserialize<OneBotCreateFolder>(SerializerOptions.DefaultOptions) is { } folder)
         {
-            await context.GroupFSCreateFolder(folder.GroupId, folder.Name);
-            return new OneBotResult(null, 0, "ok");
+            var res = await context.GroupFSCreateFolder(folder.GroupId, folder.Name);
+            return new OneBotResult(new JsonObject { { "msg", res.Item2 } }, res.Item1, res.Item1 == 0 ? "ok" : "failed");
+        }
+
+        throw new Exception();
+    }
+}
+
+[Operation("delete_group_file_folder")]
+public class DeleteGroupFileFolderOperation : IOperation
+{
+    public async Task<OneBotResult> HandleOperation(BotContext context, JsonNode? payload)
+    {
+        if (payload.Deserialize<OneBotDeleteFolder>(SerializerOptions.DefaultOptions) is { } folder)
+        {
+            var res = await context.GroupFSDeleteFolder(folder.GroupId, folder.FolderId);
+            return new OneBotResult(new JsonObject { { "msg", res.Item2 } }, res.Item1, res.Item1 == 0 ? "ok" : "failed");
         }
 
         throw new Exception();

From d04117e1893536dcaf55a6c2162790cedb6493a8 Mon Sep 17 00:00:00 2001
From: pk5ls20 <pk5ls20@outlook.com>
Date: Sat, 24 Aug 2024 22:14:27 +0800
Subject: [PATCH 04/11] [Core] Use DNS instead of IP in parsing groupfs url
 (#550)

---
 Lagrange.Core/Internal/Service/Action/GroupFSDownloadService.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lagrange.Core/Internal/Service/Action/GroupFSDownloadService.cs b/Lagrange.Core/Internal/Service/Action/GroupFSDownloadService.cs
index 72fa52b4b..040d817a0 100644
--- a/Lagrange.Core/Internal/Service/Action/GroupFSDownloadService.cs
+++ b/Lagrange.Core/Internal/Service/Action/GroupFSDownloadService.cs
@@ -38,7 +38,7 @@ protected override bool Parse(Span<byte> input, BotKeystore keystore, BotAppInfo
         var packet = Serializer.Deserialize<OidbSvcTrpcTcpBase<OidbSvcTrpcTcp0x6D6Response>>(input);
         var download = packet.Body.Download;
 
-        string url = $"https://{download.DownloadIp}:443/ftn_handler/{download.DownloadUrl.Hex(true)}/?fname=";
+        string url = $"https://{download.DownloadDns}/ftn_handler/{download.DownloadUrl.Hex(true)}/?fname=";
 
         output = GroupFSDownloadEvent.Result((int)packet.ErrorCode, url);
         extraEvents = null;

From 9042bd4651804da495cc239475e8c4b41ea61730 Mon Sep 17 00:00:00 2001
From: Decrabbityyy <99632363+Decrabbityyy@users.noreply.github.com>
Date: Sun, 25 Aug 2024 22:40:26 +0800
Subject: [PATCH 05/11] [Onebot] fix first start need input signserver (#544)

* wtf

* impossable

* use a constant string instead of hardcoding text in a single line
---
 Lagrange.OneBot/Utility/OneBotSigner.cs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Lagrange.OneBot/Utility/OneBotSigner.cs b/Lagrange.OneBot/Utility/OneBotSigner.cs
index fece40cda..2aaa218e4 100644
--- a/Lagrange.OneBot/Utility/OneBotSigner.cs
+++ b/Lagrange.OneBot/Utility/OneBotSigner.cs
@@ -15,6 +15,8 @@ public class OneBotSigner : SignProvider
 {
     private ILogger<OneBotSigner> _logger;
 
+    private const string Url = "https://sign.lagrangecore.org/api/sign/25765";
+
     private readonly string? _signServer;
 
     private readonly HttpClient _client;
@@ -27,7 +29,7 @@ public OneBotSigner(IConfiguration config, ILogger<OneBotSigner> logger, BotCont
     {
         _logger = logger;
 
-        _signServer = config["SignServerUrl"] ?? "";
+        _signServer = string.IsNullOrEmpty(config["SignServerUrl"]) ? Url : config["SignServerUrl"];
         string? signProxyUrl = config["SignProxyUrl"]; // Only support HTTP proxy
 
         _client = new HttpClient(handler: new HttpClientHandler

From 02d160a34d2fa50f8ca3bf4db4ba4a479af09de8 Mon Sep 17 00:00:00 2001
From: dogdie233 <dogdie233@163.com>
Date: Mon, 26 Aug 2024 08:18:27 +0800
Subject: [PATCH 06/11] [Core] fix: #545 (#546)

---
 Lagrange.Core/Message/Entity/ImageEntity.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lagrange.Core/Message/Entity/ImageEntity.cs b/Lagrange.Core/Message/Entity/ImageEntity.cs
index 01814e675..63468767a 100644
--- a/Lagrange.Core/Message/Entity/ImageEntity.cs
+++ b/Lagrange.Core/Message/Entity/ImageEntity.cs
@@ -88,7 +88,7 @@ IEnumerable<Elem> IMessageEntity.PackElement()
 
     IMessageEntity? IMessageEntity.UnpackElement(Elem elems)
     {
-        if (elems.CommonElem is { BusinessType: 20 or 10 } common)
+        if (elems.CommonElem is { ServiceType: 48, BusinessType: 20 or 10 } common)
         {
             var extra = Serializer.Deserialize<MsgInfo>(common.PbElem.AsSpan());
             var index = extra.MsgInfoBody[0].Index;

From 361252c3a7ab8b8ffcbb8d8be1f256f2c64add25 Mon Sep 17 00:00:00 2001
From: pk5ls20 <pk5ls20@outlook.com>
Date: Mon, 26 Aug 2024 08:20:33 +0800
Subject: [PATCH 07/11] [All] Add RenameGroupFolderOp (#551)

---
 .../Common/Interface/Api/GroupExt.cs          |  3 ++
 .../Logic/Implementation/OperationLogic.cs    |  9 ++++
 .../Event/Action/GroupFSRenameFolderEvent.cs  | 28 ++++++++++++
 .../Oidb/Request/OidbSvcTrpcTcp0x6D7_2.cs     | 26 +++++++++++
 .../Response/OidbSvcTrpcTcp0x6D7Response.cs   |  4 +-
 ....cs => OidbSvcTrpcTcp0x6D7_1_2Response.cs} |  2 +-
 .../Action/GroupFSRenameFolderService.cs      | 43 +++++++++++++++++++
 .../Core/Entity/Action/OneBotRenameFolder.cs  | 13 ++++++
 .../Core/Operation/File/GroupFSOperations.cs  | 15 +++++++
 9 files changed, 141 insertions(+), 2 deletions(-)
 create mode 100644 Lagrange.Core/Internal/Event/Action/GroupFSRenameFolderEvent.cs
 create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_2.cs
 rename Lagrange.Core/Internal/Packets/Service/Oidb/Response/{OidbSvcTrpcTcp0x6D7_1Response.cs => OidbSvcTrpcTcp0x6D7_1_2Response.cs} (88%)
 create mode 100644 Lagrange.Core/Internal/Service/Action/GroupFSRenameFolderService.cs
 create mode 100644 Lagrange.OneBot/Core/Entity/Action/OneBotRenameFolder.cs

diff --git a/Lagrange.Core/Common/Interface/Api/GroupExt.cs b/Lagrange.Core/Common/Interface/Api/GroupExt.cs
index 91dbca303..86d765464 100644
--- a/Lagrange.Core/Common/Interface/Api/GroupExt.cs
+++ b/Lagrange.Core/Common/Interface/Api/GroupExt.cs
@@ -113,6 +113,9 @@ public static Task<bool> GroupFSDelete(this BotContext bot, uint groupUin, strin
     public static Task<(int, string)> GroupFSDeleteFolder(this BotContext bot, uint groupUin, string folderId)
         => bot.ContextCollection.Business.OperationLogic.GroupFSDeleteFolder(groupUin, folderId);
     
+    public static Task<(int, string)> GroupFSRenameFolder(this BotContext bot, uint groupUin, string folderId, string newFolderName)
+        => bot.ContextCollection.Business.OperationLogic.GroupFSRenameFolder(groupUin, folderId, newFolderName);
+    
     public static Task<bool> GroupFSUpload(this BotContext bot, uint groupUin, FileEntity fileEntity, string targetDirectory = "/")
         => bot.ContextCollection.Business.OperationLogic.GroupFSUpload(groupUin, fileEntity, targetDirectory);
 
diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
index 5fa9c786b..2e38a6107 100644
--- a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
+++ b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
@@ -182,6 +182,15 @@ public async Task<bool> GroupFSDelete(uint groupUin, string fileId)
         return new(retCode, retMsg);
     }
 
+    public async Task<(int, string)> GroupFSRenameFolder(uint groupUin, string folderId, string newFolderName)
+    {
+        var groupFSDeleteFolderEvent = GroupFSRenameFolderEvent.Create(groupUin, folderId, newFolderName);
+        var events = await Collection.Business.SendEvent(groupFSDeleteFolderEvent);
+        var retCode = events.Count > 0 ? ((GroupFSRenameFolderEvent)events[0]).ResultCode : -1;
+        var retMsg = events.Count > 0 ? ((GroupFSRenameFolderEvent)events[0]).RetMsg : "";
+        return new(retCode, retMsg);
+    }
+
     public Task<bool> GroupFSUpload(uint groupUin, FileEntity fileEntity, string targetDirectory)
     {
         try
diff --git a/Lagrange.Core/Internal/Event/Action/GroupFSRenameFolderEvent.cs b/Lagrange.Core/Internal/Event/Action/GroupFSRenameFolderEvent.cs
new file mode 100644
index 000000000..259c4c8f8
--- /dev/null
+++ b/Lagrange.Core/Internal/Event/Action/GroupFSRenameFolderEvent.cs
@@ -0,0 +1,28 @@
+namespace Lagrange.Core.Internal.Event.Action;
+
+internal class GroupFSRenameFolderEvent : ProtocolEvent
+{
+    public uint GroupUin { get; }
+    
+    public string FolderId { get; } = string.Empty;
+    
+    public string NewFolderName { get; } = string.Empty;
+    
+    public string RetMsg { get; set; } = string.Empty;
+
+    private GroupFSRenameFolderEvent(uint groupUin, string folderId, string newFolderName) : base(true)
+    {
+        GroupUin = groupUin;
+        FolderId = folderId;
+        NewFolderName = newFolderName;
+    }
+
+    private GroupFSRenameFolderEvent(int resultCode, string retMsg) : base(resultCode)
+    {
+        RetMsg = retMsg;
+    }
+    
+    public static GroupFSRenameFolderEvent Create(uint groupUin, string folderId, string newFolderName) => new(groupUin, folderId, newFolderName);
+    
+    public static GroupFSRenameFolderEvent Result(int resultCode, string retMsg) => new(resultCode, retMsg);
+}
\ No newline at end of file
diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_2.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_2.cs
new file mode 100644
index 000000000..5ee84ac14
--- /dev/null
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D7_2.cs
@@ -0,0 +1,26 @@
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request;
+
+#pragma warning disable CS8618
+// ReSharper disable InconsistentNaming
+
+/// <summary>
+/// Delete Folder
+/// </summary>
+[ProtoContract]
+[OidbSvcTrpcTcp(0x6D7, 2)]
+internal class OidbSvcTrpcTcp0x6D7_2
+{
+    [ProtoMember(3)] public OidbSvcTrpcTcp0x6D7_2Rename Rename { get; set; }
+}
+
+[ProtoContract]
+internal class OidbSvcTrpcTcp0x6D7_2Rename
+{
+    [ProtoMember(1)] public uint GroupUin { get; set; }
+    
+    [ProtoMember(3)] public string FolderId { get; set; }
+    
+    [ProtoMember(4)] public string NewFolderName { get; set; }
+}
\ No newline at end of file
diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7Response.cs
index a898b54fa..344520a6f 100644
--- a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7Response.cs
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7Response.cs
@@ -10,5 +10,7 @@ internal class OidbSvcTrpcTcp0x6D7Response
 {
     [ProtoMember(1)] public OidbSvcTrpcTcp0x6D7_0Response Create { get; set; }
     
-    [ProtoMember(2)] public OidbSvcTrpcTcp0x6D7_1Response Delete { get; set; }
+    [ProtoMember(2)] public OidbSvcTrpcTcp0x6D7_1_2Response Delete { get; set; }
+    
+    [ProtoMember(3)] public OidbSvcTrpcTcp0x6D7_1_2Response Rename { get; set; }
 }
diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1_2Response.cs
similarity index 88%
rename from Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1Response.cs
rename to Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1_2Response.cs
index 25e9b3757..ca45872b1 100644
--- a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1Response.cs
+++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D7_1_2Response.cs
@@ -6,7 +6,7 @@ namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response;
 // ReSharper disable InconsistentNaming
 
 [ProtoContract]
-internal class OidbSvcTrpcTcp0x6D7_1Response
+internal class OidbSvcTrpcTcp0x6D7_1_2Response
 {
     [ProtoMember(1)] public Int32 Retcode { get; set; }
     
diff --git a/Lagrange.Core/Internal/Service/Action/GroupFSRenameFolderService.cs b/Lagrange.Core/Internal/Service/Action/GroupFSRenameFolderService.cs
new file mode 100644
index 000000000..d2b3cb26e
--- /dev/null
+++ b/Lagrange.Core/Internal/Service/Action/GroupFSRenameFolderService.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.Internal.Packets.Service.Oidb.Response;
+using Lagrange.Core.Utility.Extension;
+using ProtoBuf;
+
+namespace Lagrange.Core.Internal.Service.Action;
+
+[EventSubscribe(typeof(GroupFSRenameFolderEvent))]
+[Service("OidbSvcTrpcTcp.0x6d7_2")]
+internal class GroupFSRenameFolderService : BaseService<GroupFSRenameFolderEvent>
+{
+    protected override bool Build(GroupFSRenameFolderEvent input, BotKeystore keystore, BotAppInfo appInfo,
+        BotDeviceInfo device, out Span<byte> output, out List<Memory<byte>>? extraPackets)
+    {
+        var packet = new OidbSvcTrpcTcpBase<OidbSvcTrpcTcp0x6D7_2>(new OidbSvcTrpcTcp0x6D7_2
+        {
+            Rename = new OidbSvcTrpcTcp0x6D7_2Rename
+            {
+                GroupUin = input.GroupUin,
+                FolderId = input.FolderId,
+                NewFolderName = input.NewFolderName
+            }
+        }, false, true);
+        
+        output = packet.Serialize();
+        extraPackets = null;
+        return true;
+    }
+
+    protected override bool Parse(Span<byte> input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device,
+        out GroupFSRenameFolderEvent output, out List<ProtocolEvent>? extraEvents)
+    {
+        var packet = Serializer.Deserialize<OidbSvcTrpcTcpBase<OidbSvcTrpcTcp0x6D7Response>>(input);
+        
+        output = GroupFSRenameFolderEvent.Result(packet.Body.Rename.Retcode, packet.Body.Rename.RetMsg);
+        extraEvents = null;
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/Lagrange.OneBot/Core/Entity/Action/OneBotRenameFolder.cs b/Lagrange.OneBot/Core/Entity/Action/OneBotRenameFolder.cs
new file mode 100644
index 000000000..dbf737fa4
--- /dev/null
+++ b/Lagrange.OneBot/Core/Entity/Action/OneBotRenameFolder.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+
+namespace Lagrange.OneBot.Core.Entity.Action;
+
+[Serializable]
+public class OneBotRenameFolder
+{
+    [JsonPropertyName("group_id")] public uint GroupId { get; set; }
+    
+    [JsonPropertyName("folder_id")] public string FolderId { get; set; } = string.Empty;
+    
+    [JsonPropertyName("new_folder_name")] public string NewFolderName { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs b/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs
index f88cdff4f..e95531af8 100644
--- a/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs
+++ b/Lagrange.OneBot/Core/Operation/File/GroupFSOperations.cs
@@ -129,6 +129,21 @@ public async Task<OneBotResult> HandleOperation(BotContext context, JsonNode? pa
             return new OneBotResult(new JsonObject { { "msg", res.Item2 } }, res.Item1, res.Item1 == 0 ? "ok" : "failed");
         }
 
+        throw new Exception();
+    }
+}
+
+[Operation("rename_group_file_folder")]
+public class RenameGroupFileFolderOperation : IOperation
+{
+    public async Task<OneBotResult> HandleOperation(BotContext context, JsonNode? payload)
+    {
+        if (payload.Deserialize<OneBotRenameFolder>(SerializerOptions.DefaultOptions) is { } folder)
+        {
+            var res = await context.GroupFSRenameFolder(folder.GroupId, folder.FolderId, folder.NewFolderName);
+            return new OneBotResult(new JsonObject { { "msg", res.Item2 } }, res.Item1, res.Item1 == 0 ? "ok" : "failed");
+        }
+
         throw new Exception();
     }
 }
\ No newline at end of file

From a36ba6743f567c05ba66a70681a9bca95f00fc56 Mon Sep 17 00:00:00 2001
From: pk5ls20 <pk5ls20@outlook.com>
Date: Mon, 26 Aug 2024 08:21:40 +0800
Subject: [PATCH 08/11] [All] Add pagination to FetchGroupFSList to correctly
 fetch all group files, resolve
 https://github.com/LagrangeDev/Lagrange.Core/issues/356 (#558)

---
 .../Common/Interface/Api/GroupExt.cs          |  4 ++--
 .../Logic/Implementation/OperationLogic.cs    | 17 +++++++++++++----
 .../Internal/Event/Action/GroupFSListEvent.cs | 19 +++++++++++++------
 .../Service/Action/GroupFSViewService.cs      |  4 ++--
 4 files changed, 30 insertions(+), 14 deletions(-)

diff --git a/Lagrange.Core/Common/Interface/Api/GroupExt.cs b/Lagrange.Core/Common/Interface/Api/GroupExt.cs
index 86d765464..4e45d0fb9 100644
--- a/Lagrange.Core/Common/Interface/Api/GroupExt.cs
+++ b/Lagrange.Core/Common/Interface/Api/GroupExt.cs
@@ -95,8 +95,8 @@ public static Task<ulong> FetchGroupFSSpace(this BotContext bot, uint groupUin)
     public static Task<uint> FetchGroupFSCount(this BotContext bot, uint groupUin)
         => bot.ContextCollection.Business.OperationLogic.FetchGroupFSCount(groupUin);
 
-    public static Task<List<IBotFSEntry>> FetchGroupFSList(this BotContext bot, uint groupUin, string targetDirectory = "/", uint startIndex = 0)
-        => bot.ContextCollection.Business.OperationLogic.FetchGroupFSList(groupUin, targetDirectory, startIndex);
+    public static Task<List<IBotFSEntry>> FetchGroupFSList(this BotContext bot, uint groupUin, string targetDirectory = "/")
+        => bot.ContextCollection.Business.OperationLogic.FetchGroupFSList(groupUin, targetDirectory);
 
     public static Task<string> FetchGroupFSDownload(this BotContext bot, uint groupUin, string fileId)
         => bot.ContextCollection.Business.OperationLogic.FetchGroupFSDownload(groupUin, fileId);
diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
index 2e38a6107..59aa9169d 100644
--- a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
+++ b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs
@@ -136,11 +136,20 @@ public async Task<uint> FetchGroupFSCount(uint groupUin)
         return ((GroupFSCountEvent)events[0]).FileCount;
     }
 
-    public async Task<List<IBotFSEntry>> FetchGroupFSList(uint groupUin, string targetDirectory, uint startIndex)
+    public async Task<List<IBotFSEntry>> FetchGroupFSList(uint groupUin, string targetDirectory)
     {
-        var groupFSListEvent = GroupFSListEvent.Create(groupUin, targetDirectory, startIndex);
-        var events = await Collection.Business.SendEvent(groupFSListEvent);
-        return ((GroupFSListEvent)events[0]).FileEntries;
+        uint startIndex = 0;
+        var entries = new List<IBotFSEntry>();
+        while (true)
+        {
+            var groupFSListEvent = GroupFSListEvent.Create(groupUin, targetDirectory, startIndex, 20);
+            var events = await Collection.Business.SendEvent(groupFSListEvent);
+            if (events.Count == 0) break;
+            entries.AddRange(((GroupFSListEvent)events[0]).FileEntries);
+            if (((GroupFSListEvent)events[0]).IsEnd) break;
+            startIndex += 20;
+        }
+        return entries;
     }
 
     public async Task<string> FetchGroupFSDownload(uint groupUin, string fileId)
diff --git a/Lagrange.Core/Internal/Event/Action/GroupFSListEvent.cs b/Lagrange.Core/Internal/Event/Action/GroupFSListEvent.cs
index 34ab59235..be2e05b26 100644
--- a/Lagrange.Core/Internal/Event/Action/GroupFSListEvent.cs
+++ b/Lagrange.Core/Internal/Event/Action/GroupFSListEvent.cs
@@ -10,22 +10,29 @@ internal class GroupFSListEvent : GroupFSViewEvent
     
     public uint StartIndex { get; set; }
     
+    public uint FileCount { get; set; }
+    
+    public bool IsEnd { get; set; }
+    
     public List<IBotFSEntry> FileEntries { get; set; }
 
-    private GroupFSListEvent(uint groupUin, string targetDirectory, uint startIndex) : base(groupUin)
+    private GroupFSListEvent(uint groupUin, string targetDirectory, uint startIndex, uint fileCount) : base(groupUin)
     {
         TargetDirectory = targetDirectory;
         StartIndex = startIndex;
+        FileCount = fileCount;
+        IsEnd = false;
     }
 
-    private GroupFSListEvent(int resultCode, List<IBotFSEntry> fileEntries) : base(resultCode)
+    private GroupFSListEvent(int resultCode, List<IBotFSEntry> fileEntries, bool isEnd) : base(resultCode)
     {
         FileEntries = fileEntries;
+        IsEnd = isEnd;
     }
 
-    public static GroupFSListEvent Create(uint groupUin, string targetDirectory, uint startIndex) 
-        => new(groupUin, targetDirectory, startIndex);
+    public static GroupFSListEvent Create(uint groupUin, string targetDirectory, uint startIndex, uint fileCount) 
+        => new(groupUin, targetDirectory, startIndex, fileCount);
 
-    public static GroupFSListEvent Result(int resultCode, List<IBotFSEntry> fileEntries) 
-        => new(resultCode, fileEntries);
+    public static GroupFSListEvent Result(int resultCode, List<IBotFSEntry> fileEntries, bool isEnd) 
+        => new(resultCode, fileEntries, isEnd);
 }
\ No newline at end of file
diff --git a/Lagrange.Core/Internal/Service/Action/GroupFSViewService.cs b/Lagrange.Core/Internal/Service/Action/GroupFSViewService.cs
index b3b93d904..4b6ca54db 100644
--- a/Lagrange.Core/Internal/Service/Action/GroupFSViewService.cs
+++ b/Lagrange.Core/Internal/Service/Action/GroupFSViewService.cs
@@ -61,7 +61,7 @@ protected override bool Parse(Span<byte> input, BotKeystore keystore, BotAppInfo
 
         extraEvents = null;
 
-        if (packet.Body.List != null)
+        if (packet.Body.List is { RetCode: 0 })
         {
             var items = packet.Body.List.Items ?? new List<OidbSvcTrpcTcp0x6D8_1ResponseItem>();
             var fileEntries = items.Select(x =>
@@ -83,7 +83,7 @@ protected override bool Parse(Span<byte> input, BotKeystore keystore, BotAppInfo
 
                 return entry;
             }).ToList();
-            output = GroupFSListEvent.Result((int)packet.ErrorCode, fileEntries);
+            output = GroupFSListEvent.Result((int)packet.ErrorCode, fileEntries, packet.Body.List.IsEnd);
             return true;
         }
 

From c2e7fe99ca7da21e7b031b13d52f4488eeb39ef9 Mon Sep 17 00:00:00 2001
From: CwkDark <177549718+CwkDark@users.noreply.github.com>
Date: Mon, 26 Aug 2024 08:23:33 +0800
Subject: [PATCH 09/11] [Core] public file id (#552)

---
 Lagrange.Core/Common/Entity/BotFileEntry.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lagrange.Core/Common/Entity/BotFileEntry.cs b/Lagrange.Core/Common/Entity/BotFileEntry.cs
index e077485b4..393e8e66f 100644
--- a/Lagrange.Core/Common/Entity/BotFileEntry.cs
+++ b/Lagrange.Core/Common/Entity/BotFileEntry.cs
@@ -3,7 +3,7 @@ namespace Lagrange.Core.Common.Entity;
 [Serializable]
 public class BotFileEntry : IBotFSEntry
 {
-    internal string FileId { get; }
+    public string FileId { get; }
     
     public string FileName { get; }
     

From 6c930b71611858927aff84c5342ef76ef97a46a4 Mon Sep 17 00:00:00 2001
From: CwkDark <177549718+CwkDark@users.noreply.github.com>
Date: Mon, 26 Aug 2024 08:25:36 +0800
Subject: [PATCH 10/11] [Core] public image entity sub type (#554)

---
 Lagrange.Core/Message/Entity/ImageEntity.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Lagrange.Core/Message/Entity/ImageEntity.cs b/Lagrange.Core/Message/Entity/ImageEntity.cs
index 63468767a..350217e34 100644
--- a/Lagrange.Core/Message/Entity/ImageEntity.cs
+++ b/Lagrange.Core/Message/Entity/ImageEntity.cs
@@ -40,7 +40,7 @@ public class ImageEntity : IMessageEntity
 
     internal string? Summary { get; set; }
     
-    internal int SubType { get; set; }
+    public int SubType { get; set; }
 
     public ImageEntity() { }
 

From aa7ca60b5c0109f4f2a1c7d21e0e4dbbf291fe00 Mon Sep 17 00:00:00 2001
From: sweetymajo <115923988+sweetymajo@users.noreply.github.com>
Date: Mon, 26 Aug 2024 20:58:30 +0800
Subject: [PATCH 11/11] MultiMsg supports custom detail text. (#508)

---
 .../Message/Entity/MultiMsgEntity.cs          | 28 +++++++++++++------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/Lagrange.Core/Message/Entity/MultiMsgEntity.cs b/Lagrange.Core/Message/Entity/MultiMsgEntity.cs
index 7a8354ddf..fdbbbd70d 100644
--- a/Lagrange.Core/Message/Entity/MultiMsgEntity.cs
+++ b/Lagrange.Core/Message/Entity/MultiMsgEntity.cs
@@ -20,6 +20,8 @@ public class MultiMsgEntity : IMessageEntity
 
     public List<MessageChain> Chains { get; }
 
+    public string? DetailStr { get; set; }
+
     internal MultiMsgEntity() => Chains = new List<MessageChain>();
 
     public MultiMsgEntity(string resId)
@@ -28,10 +30,11 @@ public MultiMsgEntity(string resId)
         Chains = new List<MessageChain>();
     }
 
-    public MultiMsgEntity(uint? groupUin, List<MessageChain> chains)
+    public MultiMsgEntity(uint? groupUin, List<MessageChain> chains, string? detail = null)
     {
         GroupUin = groupUin;
         Chains = chains;
+        DetailStr = detail;
     }
 
     IEnumerable<Elem> IMessageEntity.PackElement()
@@ -73,19 +76,26 @@ IEnumerable<Elem> IMessageEntity.PackElement()
             View = "contact"
         };
 
-        if (!Chains.Select(x => x.GetEntity<TextEntity>()).Any())
+        if (!string.IsNullOrEmpty(DetailStr))
         {
-            json.Meta.Detail.News.Add(new News { Text = "[This message is send from Lagrange.Core]" });
+            json.Meta.Detail.News.Add(new News { Text = DetailStr });
         }
         else
         {
-            for (int i = 0; i < count; i++)
+            if (!Chains.Select(x => x.GetEntity<TextEntity>()).Any())
+            {
+                json.Meta.Detail.News.Add(new News { Text = "[This message is send from Lagrange.Core]" });
+            }
+            else
             {
-                var chain = Chains[i];
-                var member = chain.GroupMemberInfo;
-                var friend = chain.FriendInfo;
-                string text = $"{member?.MemberCard ?? member?.MemberName ?? friend?.Nickname}: {chain.ToPreviewText()}";
-                json.Meta.Detail.News.Add(new News { Text = text });
+                for (int i = 0; i < count; i++)
+                {
+                    var chain = Chains[i];
+                    var member = chain.GroupMemberInfo;
+                    var friend = chain.FriendInfo;
+                    string text = $"{member?.MemberCard ?? member?.MemberName ?? friend?.Nickname}: {chain.ToPreviewText()}";
+                    json.Meta.Detail.News.Add(new News { Text = text });
+                }
             }
         }