Skip to content

Commit af03489

Browse files
Lilylttsisi0318
andauthored
[Core] Add FlashTransfer Logic, Fix Aot and mac sending image issue (#59)
Co-authored-by: sisi0318 <sisi0318@users.noreply.github.com>
1 parent eb0f785 commit af03489

6 files changed

Lines changed: 168 additions & 2 deletions

File tree

Lagrange.Core/BotContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal BotContext(BotConfig config, BotKeystore keystore, BotAppInfo appInfo)
2323
SocketContext = new SocketContext(this);
2424
EventContext = new EventContext(this);
2525
HighwayContext = new HighwayContext(this);
26+
FlashTransferContext = new FlashTransferContext(this);
2627
}
2728

2829
public BotConfig Config { get; }
@@ -41,6 +42,7 @@ internal BotContext(BotConfig config, BotKeystore keystore, BotAppInfo appInfo)
4142
internal EventContext EventContext { get; }
4243

4344
internal HighwayContext HighwayContext { get; }
45+
internal FlashTransferContext FlashTransferContext { get; }
4446

4547
#region Shortcut Methods
4648

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System.Security.Cryptography;
2+
using Lagrange.Core.Internal.Packets.Service;
3+
using Lagrange.Core.Utility;
4+
using Lagrange.Core.Utility.Cryptography;
5+
using Lagrange.Core.Utility.Extension;
6+
7+
namespace Lagrange.Core.Internal.Context;
8+
9+
public class FlashTransferContext
10+
{
11+
private const string Tag = nameof(FlashTransferContext);
12+
private readonly BotContext _botContext;
13+
private readonly HttpClient _client;
14+
private readonly string? _url;
15+
private const uint ChunkSize = 1024 * 1024;
16+
17+
internal FlashTransferContext(BotContext botContext)
18+
{
19+
_botContext = botContext;
20+
_client = new HttpClient();
21+
_client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
22+
_url = "https://multimedia.qfile.qq.com/sliceupload";
23+
}
24+
25+
public async Task<bool> UploadFile(string uKey, Stream bodyStream)
26+
{
27+
var sha1StateVs = new FlashTransferSha1StateV { State = [] };
28+
var chunkCount = (uint)((bodyStream.Length + ChunkSize - 1) / ChunkSize);
29+
30+
var sha1Stream = new Sha1Stream();
31+
for (uint i = 0; i < chunkCount; i++)
32+
{
33+
if (i != chunkCount - 1)
34+
{
35+
var accLength = (int)((i + 1) * ChunkSize);
36+
var accBuffer = new byte[accLength];
37+
38+
bodyStream.Position = 0;
39+
await bodyStream.ReadExactlyAsync(accBuffer, 0, accLength);
40+
41+
var accSpan = accBuffer.AsSpan();
42+
var digest = new byte[20];
43+
sha1Stream.Update(accSpan);
44+
sha1Stream.Hash(digest, false);
45+
sha1Stream.Reset();
46+
sha1StateVs.State.Add(digest.ToArray());
47+
}
48+
else
49+
{
50+
bodyStream.Position = 0;
51+
sha1StateVs.State.Add(bodyStream.Sha1());
52+
}
53+
}
54+
55+
for (uint i = 0; i < chunkCount; i++)
56+
{
57+
var chunkStart = (long)(i * ChunkSize);
58+
var chunkLength = (int)Math.Min(ChunkSize, bodyStream.Length - chunkStart);
59+
60+
bodyStream.Position = chunkStart;
61+
var uploadBuffer = new byte[chunkLength];
62+
await bodyStream.ReadExactlyAsync(uploadBuffer, 0, chunkLength);
63+
64+
var success = await UploadChunk(uKey, (uint)chunkStart, sha1StateVs, uploadBuffer);
65+
if (!success) return false;
66+
}
67+
68+
return true;
69+
}
70+
71+
private async Task<bool> UploadChunk(string uKey, uint start, FlashTransferSha1StateV chunkSha1S, byte[] body)
72+
{
73+
var req = new FlashTransferUploadReq
74+
{
75+
FieId1 = 0,
76+
AppId = 1407,
77+
FileId3 = 2,
78+
Body = new FlashTransferUploadBody
79+
{
80+
FieId1 = [],
81+
UKey = uKey,
82+
Start = start,
83+
End = (uint)(start + body.Length - 1),
84+
Sha1 = SHA1.HashData(body),
85+
Sha1StateV = chunkSha1S,
86+
Body = body
87+
}
88+
};
89+
var payload = ProtoHelper.Serialize(req).ToArray();
90+
var request = new HttpRequestMessage(HttpMethod.Post, _url)
91+
{
92+
Headers =
93+
{
94+
{ "Accept", "*/*" },
95+
{ "Expect", "100-continue" },
96+
{ "Connection", "Keep-Alive" }
97+
},
98+
Content = new ByteArrayContent(payload)
99+
};
100+
var response = await _client.SendAsync(request);
101+
var responseBytes = await response.Content.ReadAsByteArrayAsync();
102+
var resp = ProtoHelper.Deserialize<FlashTransferUploadResp>(responseBytes);
103+
104+
if (resp.Status != "success")
105+
{
106+
_botContext.LogError(Tag,
107+
$"FlashTransfer Upload chunk {start} failed: {resp.Status}");
108+
return false;
109+
}
110+
111+
return true;
112+
}
113+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Lagrange.Proto;
2+
3+
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 'required' 修饰符或声明为可以为 null。
4+
namespace Lagrange.Core.Internal.Packets.Service;
5+
6+
[ProtoPackable]
7+
internal partial class FlashTransferUploadReq
8+
{
9+
[ProtoMember(1)] public uint FieId1 { get; set; } // 0
10+
[ProtoMember(2)] public uint AppId { get; set; } // 1407
11+
[ProtoMember(3)] public uint FileId3 { get; set; } // 0
12+
[ProtoMember(107)] public FlashTransferUploadBody Body { get; set; }
13+
}
14+
15+
[ProtoPackable]
16+
internal partial class FlashTransferUploadBody
17+
{
18+
[ProtoMember(1)] public byte[] FieId1 { get; set; } // Empty
19+
[ProtoMember(2)] public string UKey { get; set; }
20+
[ProtoMember(3)] public uint Start { get; set; } // Start
21+
[ProtoMember(4)] public uint End { get; set; } // Start + Size - 1
22+
[ProtoMember(5)] public byte[] Sha1 { get; set; }
23+
[ProtoMember(6)] public FlashTransferSha1StateV Sha1StateV { get; set; }
24+
[ProtoMember(7)] public byte[] Body { get; set; }
25+
}
26+
27+
[ProtoPackable]
28+
internal partial class FlashTransferSha1StateV
29+
{
30+
[ProtoMember(1)] public List<byte[]> State { get; set; }
31+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Lagrange.Proto;
2+
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 'required' 修饰符或声明为可以为 null。
3+
4+
namespace Lagrange.Core.Internal.Packets.Service;
5+
6+
[ProtoPackable]
7+
internal partial class FlashTransferUploadResp
8+
{
9+
[ProtoMember(5)] public string Status { get; set; }
10+
}

Lagrange.Core/Message/Entities/ImageEntity.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Numerics;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
24
using Lagrange.Core.Internal.Events.Message;
35
using Lagrange.Core.Internal.Packets.Message;
46
using Lagrange.Core.Internal.Packets.Service;
@@ -43,7 +45,15 @@ public override async Task Preprocess(BotContext context, BotMessage message)
4345

4446
if (result.Ext != null)
4547
{
46-
await context.HighwayContext.UploadFile(Stream.Value, message.IsGroup() ? 1004 : 1003, ProtoHelper.Serialize(result.Ext));
48+
// Aot 和 MacOS 下使用 FlashTransfer 上传
49+
if (RuntimeFeature.IsDynamicCodeCompiled && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
50+
{
51+
await context.HighwayContext.UploadFile(Stream.Value, message.IsGroup() ? 1004 : 1003, ProtoHelper.Serialize(result.Ext));
52+
}
53+
else
54+
{
55+
await context.FlashTransferContext.UploadFile(result.Ext.UKey, Stream.Value);
56+
}
4757
}
4858
}
4959
finally

Lagrange.Core/Utility/Cryptography/Sha1Stream.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public Sha1Stream() // Initialize SHA1 context
3030
Reset();
3131
}
3232

33-
private void Reset()
33+
public void Reset()
3434
{
3535
_state[0] = 0x67452301;
3636
_state[1] = 0xEFCDAB89;

0 commit comments

Comments
 (0)