From 7a8a62beac596421983e2bf6597f8595fd26c0db Mon Sep 17 00:00:00 2001 From: Lilyltt <1806552019@qq.com> Date: Thu, 11 Sep 2025 18:46:27 +0800 Subject: [PATCH 1/4] [Core] Add FlashTransfer Logic, Fix Aot and mac sending image issue --- Lagrange.Core/BotContext.cs | 2 + .../Internal/Context/FlashTransferContext.cs | 127 ++++++++++++++++++ .../Packets/Service/FlashTransferUploadReq.cs | 31 +++++ .../Service/FlashTransferUploadResp.cs | 10 ++ Lagrange.Core/Message/Entities/ImageEntity.cs | 12 +- 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 Lagrange.Core/Internal/Context/FlashTransferContext.cs create mode 100644 Lagrange.Core/Internal/Packets/Service/FlashTransferUploadReq.cs create mode 100644 Lagrange.Core/Internal/Packets/Service/FlashTransferUploadResp.cs diff --git a/Lagrange.Core/BotContext.cs b/Lagrange.Core/BotContext.cs index f702ff6f..c938e2c6 100644 --- a/Lagrange.Core/BotContext.cs +++ b/Lagrange.Core/BotContext.cs @@ -23,6 +23,7 @@ internal BotContext(BotConfig config, BotKeystore keystore, BotAppInfo appInfo) SocketContext = new SocketContext(this); EventContext = new EventContext(this); HighwayContext = new HighwayContext(this); + FlashTransferContext = new FlashTransferContext(this); } public BotConfig Config { get; } @@ -41,6 +42,7 @@ internal BotContext(BotConfig config, BotKeystore keystore, BotAppInfo appInfo) internal EventContext EventContext { get; } internal HighwayContext HighwayContext { get; } + internal FlashTransferContext FlashTransferContext { get; } #region Shortcut Methods diff --git a/Lagrange.Core/Internal/Context/FlashTransferContext.cs b/Lagrange.Core/Internal/Context/FlashTransferContext.cs new file mode 100644 index 00000000..454672f1 --- /dev/null +++ b/Lagrange.Core/Internal/Context/FlashTransferContext.cs @@ -0,0 +1,127 @@ +using System.Runtime.Intrinsics.Arm; +using System.Security.Cryptography; +using Lagrange.Core.Internal.Packets.Service; +using Lagrange.Core.Utility; +using Lagrange.Core.Utility.Cryptography; + +namespace Lagrange.Core.Internal.Context; + +public class FlashTransferContext +{ + private const string Tag = nameof(HighwayContext); + private readonly BotContext _botContext; + private readonly HttpClient _client; + private readonly string? _url; + private const uint ChunkSize = 1024 * 1024; + + internal FlashTransferContext(BotContext botContext) + { + _botContext = botContext; + _client = new HttpClient(); + _client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip"); + _url = "https://multimedia.qfile.qq.com/sliceupload"; + } + + public async Task UploadFile(string uKey, Stream bodyStream) + { + byte[] body; + if (bodyStream is MemoryStream ms) + { + body = ms.ToArray(); + } + else + { + bodyStream.Position = 0; + var buffer = new byte[bodyStream.Length]; + await bodyStream.ReadExactlyAsync(buffer, 0, buffer.Length); + body = buffer; + } + + return await UploadFile(uKey, body); + } + + public async Task UploadFile(string uKey, byte[] body) + { + var chunkSha1S = new FlashTransferSha1StateV { State = [] }; + var chunkBuffers = new List(); + var chunkCount = (uint)((body.Length + ChunkSize - 1) / ChunkSize); + + using var accStream = new MemoryStream(); + for (uint i = 0; i < chunkCount; i++) + { + var chunkBuffer = body.AsSpan((int)(i * ChunkSize), (int)Math.Min(ChunkSize, body.Length - i * ChunkSize)) + .ToArray(); + chunkBuffers.Add(chunkBuffer); + + if (i != chunkCount - 1) + { + accStream.Write(chunkBuffer, 0, chunkBuffer.Length); + var accBytes = accStream.ToArray(); + var sha1Stream = new Sha1Stream(); + var digest = new byte[20]; + sha1Stream.Update(accBytes); + sha1Stream.Hash(digest, false); + chunkSha1S.State.Add(digest); + } + else + { + chunkSha1S.State.Add(SHA1.HashData(body)); + } + } + + // cnm闹禅tx为什么不能并发,回答我 + foreach (var chunkBuffer in chunkBuffers) + { + var success = await UploadChunk(uKey, (uint)(chunkBuffers.IndexOf(chunkBuffer) * ChunkSize), chunkSha1S, chunkBuffer); + if (!success) + { + return false; + } + } + + return true; + } + + private async Task UploadChunk(string uKey, uint start, FlashTransferSha1StateV chunkSha1S, byte[] body) + { + var req = new FlashTransferUploadReq + { + FieId1 = 0, + AppId = 1407, + FileId3 = 2, + Body = new FlashTransferUploadBody + { + FieId1 = [], + UKey = uKey, + Start = start, + End = (uint)(start + body.Length - 1), + Sha1 = SHA1.HashData(body), + Sha1StateV = chunkSha1S, + Body = body + } + }; + var payload = ProtoHelper.Serialize(req).ToArray(); + var request = new HttpRequestMessage(HttpMethod.Post, _url) + { + Headers = + { + { "Accept", "*/*" }, + { "Expect", "100-continue" }, + { "Connection", "Keep-Alive" } + }, + Content = new ByteArrayContent(payload) + }; + var response = await _client.SendAsync(request); + var responseBytes = await response.Content.ReadAsByteArrayAsync(); + var resp = ProtoHelper.Deserialize(responseBytes); + + if (resp.Status != "success") + { + _botContext.LogError(Tag, + $"FlashTransfer Upload chunk {start} failed: {resp.Status}"); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/FlashTransferUploadReq.cs b/Lagrange.Core/Internal/Packets/Service/FlashTransferUploadReq.cs new file mode 100644 index 00000000..f44213e1 --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/FlashTransferUploadReq.cs @@ -0,0 +1,31 @@ +using Lagrange.Proto; + +#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 'required' 修饰符或声明为可以为 null。 +namespace Lagrange.Core.Internal.Packets.Service; + +[ProtoPackable] +internal partial class FlashTransferUploadReq +{ + [ProtoMember(1)] public uint FieId1 { get; set; } // 0 + [ProtoMember(2)] public uint AppId { get; set; } // 1407 + [ProtoMember(3)] public uint FileId3 { get; set; } // 0 + [ProtoMember(107)] public FlashTransferUploadBody Body { get; set; } +} + +[ProtoPackable] +internal partial class FlashTransferUploadBody +{ + [ProtoMember(1)] public byte[] FieId1 { get; set; } // Empty + [ProtoMember(2)] public string UKey { get; set; } + [ProtoMember(3)] public uint Start { get; set; } // Start + [ProtoMember(4)] public uint End { get; set; } // Start + Size - 1 + [ProtoMember(5)] public byte[] Sha1 { get; set; } + [ProtoMember(6)] public FlashTransferSha1StateV Sha1StateV { get; set; } + [ProtoMember(7)] public byte[] Body { get; set; } +} + +[ProtoPackable] +internal partial class FlashTransferSha1StateV +{ + [ProtoMember(1)] public List State { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/FlashTransferUploadResp.cs b/Lagrange.Core/Internal/Packets/Service/FlashTransferUploadResp.cs new file mode 100644 index 00000000..f576555f --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/FlashTransferUploadResp.cs @@ -0,0 +1,10 @@ +using Lagrange.Proto; +#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 'required' 修饰符或声明为可以为 null。 + +namespace Lagrange.Core.Internal.Packets.Service; + +[ProtoPackable] +internal partial class FlashTransferUploadResp +{ + [ProtoMember(5)] public string Status { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Message/Entities/ImageEntity.cs b/Lagrange.Core/Message/Entities/ImageEntity.cs index 3af59c02..7371ee24 100644 --- a/Lagrange.Core/Message/Entities/ImageEntity.cs +++ b/Lagrange.Core/Message/Entities/ImageEntity.cs @@ -1,4 +1,6 @@ using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Lagrange.Core.Internal.Events.Message; using Lagrange.Core.Internal.Packets.Message; using Lagrange.Core.Internal.Packets.Service; @@ -43,7 +45,15 @@ public override async Task Preprocess(BotContext context, BotMessage message) if (result.Ext != null) { - await context.HighwayContext.UploadFile(Stream.Value, message.IsGroup() ? 1004 : 1003, ProtoHelper.Serialize(result.Ext)); + // Aot 和 MacOS 下使用 FlashTransfer 上传 + if (RuntimeFeature.IsDynamicCodeCompiled || !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + await context.HighwayContext.UploadFile(Stream.Value, message.IsGroup() ? 1004 : 1003, ProtoHelper.Serialize(result.Ext)); + } + else + { + await context.FlashTransferContext.UploadFile(result.Ext.UKey, Stream.Value); + } } } finally From 0cf8e1176b324011d0726f44584427e88d65aa97 Mon Sep 17 00:00:00 2001 From: TowaNoah <103859536+Lilyltt@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:50:40 +0800 Subject: [PATCH 2/4] [Core] Fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Lagrange.Core/Internal/Context/FlashTransferContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lagrange.Core/Internal/Context/FlashTransferContext.cs b/Lagrange.Core/Internal/Context/FlashTransferContext.cs index 454672f1..5bc434bc 100644 --- a/Lagrange.Core/Internal/Context/FlashTransferContext.cs +++ b/Lagrange.Core/Internal/Context/FlashTransferContext.cs @@ -8,7 +8,7 @@ namespace Lagrange.Core.Internal.Context; public class FlashTransferContext { - private const string Tag = nameof(HighwayContext); + private const string Tag = nameof(FlashTransferContext); private readonly BotContext _botContext; private readonly HttpClient _client; private readonly string? _url; From f77abe717ec412854c22e120f3adec50d25d1489 Mon Sep 17 00:00:00 2001 From: TowaNoah <103859536+Lilyltt@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:54:47 +0800 Subject: [PATCH 3/4] [Core] Fix Operator which makes wrong logic --- Lagrange.Core/Message/Entities/ImageEntity.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lagrange.Core/Message/Entities/ImageEntity.cs b/Lagrange.Core/Message/Entities/ImageEntity.cs index 7371ee24..66d7bc33 100644 --- a/Lagrange.Core/Message/Entities/ImageEntity.cs +++ b/Lagrange.Core/Message/Entities/ImageEntity.cs @@ -46,7 +46,7 @@ public override async Task Preprocess(BotContext context, BotMessage message) if (result.Ext != null) { // Aot 和 MacOS 下使用 FlashTransfer 上传 - if (RuntimeFeature.IsDynamicCodeCompiled || !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + if (RuntimeFeature.IsDynamicCodeCompiled && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { await context.HighwayContext.UploadFile(Stream.Value, message.IsGroup() ? 1004 : 1003, ProtoHelper.Serialize(result.Ext)); } @@ -129,4 +129,4 @@ internal override Elem[] Build() return null; } -} \ No newline at end of file +} From 534323f6cf3f1f5b294440827e5203071874588a Mon Sep 17 00:00:00 2001 From: TowaNoah <103859536+Lilyltt@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:54:47 +0800 Subject: [PATCH 4/4] [Core] Fix Operator which makes wrong logic Co-authored-by: sisi0318