Skip to content

Commit 22c3292

Browse files
committed
feat: 新增 /upload_flash_transfer 文件闪传上传
1 parent 32138db commit 22c3292

10 files changed

Lines changed: 1090 additions & 18 deletions

File tree

Lagrange.Core/Common/Interface/MessageExt.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Lagrange.Core.Internal.Logic;
22
using Lagrange.Core.Message;
3+
using Lagrange.Core.Common.Response;
34

45
namespace Lagrange.Core.Common.Interface;
56

@@ -32,6 +33,9 @@ public static Task<List<BotMessage>> GetC2CMessage(this BotContext context, long
3233
public static Task<string> SendGroupFile(this BotContext context, long groupUin, Stream fileStream, string? fileName = null, string parentDirectory = "/")
3334
=> context.EventContext.GetLogic<OperationLogic>().SendGroupFile(groupUin, fileStream, fileName, parentDirectory);
3435

36+
public static Task<BotFlashTransferUpload> UploadFlashTransfer(this BotContext context, IReadOnlyList<(Stream Stream, string? FileName)> files, string? title = null)
37+
=> context.EventContext.GetLogic<OperationLogic>().UploadFlashTransfer(files, title);
38+
3539
public static Task RecallMessage(this BotContext context, BotMessage message)
3640
=> context.EventContext.GetLogic<MessagingLogic>().RecallMessage(message);
3741

@@ -61,4 +65,4 @@ public static Task GroupRename(this BotContext context, long groupUin, string na
6165

6266
public static Task GroupQuit(this BotContext context, long groupUin)
6367
=> context.EventContext.GetLogic<OperationLogic>().GroupQuit(groupUin);
64-
}
68+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Lagrange.Core.Common.Response;
2+
3+
public class BotFlashTransferUpload(string fileSetId, List<string> fileIds, string shareLink)
4+
{
5+
public string FileSetId { get; } = fileSetId;
6+
7+
public List<string> FileIds { get; } = fileIds;
8+
9+
public string ShareLink { get; } = shareLink;
10+
}

Lagrange.Core/Internal/Context/FlashTransferContext.cs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Security.Cryptography;
1+
using System.Security.Cryptography;
22
using Lagrange.Core.Internal.Packets.Service;
33
using Lagrange.Core.Utility;
44
using Lagrange.Core.Utility.Cryptography;
@@ -23,6 +23,28 @@ internal FlashTransferContext(BotContext botContext)
2323
}
2424

2525
public async Task<bool> UploadFile(string uKey, uint appId, Stream bodyStream)
26+
{
27+
return await UploadFile(uKey, appId, 2, bodyStream, null, _url);
28+
}
29+
30+
public async Task<bool> UploadFile(string uploadToken, string uploadHost, uint appId, uint uploadIndex, string fileSetId, string fileId, uint bindingStage, uint fileType, uint bindingField5, uint bindingField6, Stream bodyStream)
31+
{
32+
var binding = new FlashTransferUploadFileBinding
33+
{
34+
FileSetId = fileSetId,
35+
FileSetIdDup = fileSetId,
36+
FileId = fileId,
37+
Stage = bindingStage,
38+
Field5 = bindingField5,
39+
Field6 = bindingField6,
40+
FileType = fileType,
41+
FileIdDup = fileId,
42+
};
43+
44+
return await UploadFile(uploadToken, appId, uploadIndex, bodyStream, binding, $"https://{uploadHost}/sliceupload");
45+
}
46+
47+
private async Task<bool> UploadFile(string uKey, uint appId, uint uploadIndex, Stream bodyStream, FlashTransferUploadFileBinding? binding, string? url)
2648
{
2749
var sha1StateVs = new FlashTransferSha1StateV { State = [] };
2850
var chunkCount = (uint)((bodyStream.Length + ChunkSize - 1) / ChunkSize);
@@ -61,33 +83,35 @@ public async Task<bool> UploadFile(string uKey, uint appId, Stream bodyStream)
6183
var uploadBuffer = new byte[chunkLength];
6284
await bodyStream.ReadExactlyAsync(uploadBuffer, 0, chunkLength);
6385

64-
var success = await UploadChunk(uKey, appId, (uint)chunkStart, sha1StateVs, uploadBuffer);
86+
var success = await UploadChunk(uKey, appId, uploadIndex, (uint)chunkStart, sha1StateVs, uploadBuffer, binding, url);
6587
if (!success) return false;
6688
}
6789

6890
return true;
6991
}
7092

71-
private async Task<bool> UploadChunk(string uKey, uint appId, uint start, FlashTransferSha1StateV chunkSha1S, byte[] body)
93+
private async Task<bool> UploadChunk(string uKey, uint appId, uint uploadIndex, uint start, FlashTransferSha1StateV chunkSha1S, byte[] body, FlashTransferUploadFileBinding? binding, string? url)
7294
{
95+
byte[] chunkSha1 = SHA1.HashData(body);
7396
var req = new FlashTransferUploadReq
7497
{
75-
FieId1 = 0,
98+
FileId = 0,
7699
AppId = appId,
77-
FileId3 = 2,
100+
UploadIndex = uploadIndex,
78101
Body = new FlashTransferUploadBody
79102
{
80-
FieId1 = [],
103+
FileId = [],
81104
UKey = uKey,
82105
Start = start,
83106
End = (uint)(start + body.Length - 1),
84-
Sha1 = SHA1.HashData(body),
85-
Sha1StateV = chunkSha1S,
86-
Body = body
107+
Sha1 = chunkSha1,
108+
Sha1StateV = binding == null ? chunkSha1S : new FlashTransferSha1StateV { State = [chunkSha1] },
109+
Body = body,
110+
FileBinding = binding
87111
}
88112
};
89113
var payload = ProtoHelper.Serialize(req).ToArray();
90-
var request = new HttpRequestMessage(HttpMethod.Post, _url)
114+
var request = new HttpRequestMessage(HttpMethod.Post, url)
91115
{
92116
Headers =
93117
{
@@ -104,10 +128,10 @@ private async Task<bool> UploadChunk(string uKey, uint appId, uint start, FlashT
104128
if (resp.Status != "success")
105129
{
106130
_botContext.LogError(Tag,
107-
$"FlashTransfer Upload chunk {start} failed: {resp.Status}");
131+
$"FlashTransfer Upload chunk {start} failed: {resp.Status} appId: {appId}, uploadIndex: {uploadIndex}, keyLength: {uKey?.Length ?? 0}");
108132
return false;
109133
}
110134

111135
return true;
112136
}
113-
}
137+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
using Lagrange.Core.Utility.Extension;
2+
3+
namespace Lagrange.Core.Internal.Events.System;
4+
5+
internal class FlashTransferFile(string fileId, uint index, string fileName, Stream stream)
6+
{
7+
public const uint DefaultFileType = 11;
8+
9+
public string FileId { get; } = fileId;
10+
11+
public uint Index { get; } = index;
12+
13+
public string FileName { get; } = fileName;
14+
15+
public uint FileType { get; } = ResolveFileType(fileName);
16+
17+
public FlashTransferFileCategory Category => ResolveCategory(FileType);
18+
19+
public Stream Stream { get; } = stream;
20+
21+
public byte[] FileSha1 { get; } = stream.Sha1();
22+
23+
public byte[] FileMd5 { get; } = stream.Md5();
24+
25+
public static List<FlashTransferFileCategoryStatistic> CreateCategoryStatistics(IReadOnlyList<FlashTransferFile> files)
26+
{
27+
return files
28+
.GroupBy(file => file.Category)
29+
.OrderBy(group => group.Key)
30+
.Select(group => new FlashTransferFileCategoryStatistic(group.Key, group.Count(), $"{ResolveCategoryName(group.Key)}({group.Count()})"))
31+
.ToList();
32+
}
33+
34+
private static uint ResolveFileType(string fileName)
35+
{
36+
return Path.GetExtension(fileName).ToLowerInvariant() switch
37+
{
38+
".mp3" or ".wav" or ".aac" or ".flac" => 1,
39+
".mp4" or ".avi" or ".mkv" or ".mov" or ".3gp" or ".mpeg" or ".rmvb" or ".rm" or ".wmv" or ".flv" or ".asf" or ".webm" or ".mpg" or ".vob" or ".m4v" or ".f4v" => 2,
40+
".doc" or ".docx" => 3,
41+
".zip" or ".rar" or ".tar" or ".gz" => 4,
42+
".apk" => 5,
43+
".xls" or ".xlsx" => 6,
44+
".ppt" or ".pptx" => 7,
45+
".html" or ".htm" => 8,
46+
".pdf" => 9,
47+
".txt" => 10,
48+
".psd" => 12,
49+
".pt" or ".pth" or ".onnx" or ".model" or ".mlmodel" => 15,
50+
".ttf" or ".otf" => 16,
51+
".ipa" => 17,
52+
".key" => 18,
53+
".note" => 19,
54+
".numbers" => 20,
55+
".pages" => 21,
56+
".sketch" => 22,
57+
".dmg" => 23,
58+
".pkg" => 24,
59+
".jpg" or ".jpeg" or ".png" or ".gif" or ".bmp" or ".webp" or ".heic" or ".heif" or ".dib" or ".ico" or ".avif" or ".tif" or ".tiff" => 26,
60+
_ => DefaultFileType
61+
};
62+
}
63+
64+
private static FlashTransferFileCategory ResolveCategory(uint fileType)
65+
{
66+
return fileType switch
67+
{
68+
3 or 6 or 7 or 9 or 10 or 13 or 18 or 19 or 20 or 21 => FlashTransferFileCategory.Document,
69+
26 => FlashTransferFileCategory.Image,
70+
2 => FlashTransferFileCategory.Video,
71+
4 => FlashTransferFileCategory.Archive,
72+
25 => FlashTransferFileCategory.Folder,
73+
_ => FlashTransferFileCategory.Other
74+
};
75+
}
76+
77+
private static string ResolveCategoryName(FlashTransferFileCategory category)
78+
{
79+
return category switch
80+
{
81+
FlashTransferFileCategory.Document => "文档",
82+
FlashTransferFileCategory.Image => "图片",
83+
FlashTransferFileCategory.Video => "视频",
84+
FlashTransferFileCategory.Archive => "压缩包",
85+
FlashTransferFileCategory.Folder => "文件夹",
86+
_ => "其他"
87+
};
88+
}
89+
}
90+
91+
internal enum FlashTransferFileCategory : uint
92+
{
93+
Document = 1,
94+
Image = 2,
95+
Video = 3,
96+
Archive = 4,
97+
Folder = 5,
98+
Other = 6
99+
}
100+
101+
internal readonly record struct FlashTransferFileCategoryStatistic(FlashTransferFileCategory Category, int Count, string DisplayName);
102+
103+
internal class FlashTransferCreateFileSetEventReq(string title, string asciiTitle, List<FlashTransferFile> files) : ProtocolEvent
104+
{
105+
public string Title { get; } = title;
106+
107+
public string AsciiTitle { get; } = asciiTitle;
108+
109+
public List<FlashTransferFile> Files { get; } = files;
110+
111+
public List<FlashTransferFileCategoryStatistic> CategoryStatistics { get; } = FlashTransferFile.CreateCategoryStatistics(files);
112+
}
113+
114+
internal class FlashTransferCreateFileSetEventResp(string fileSetId, string shareLink) : ProtocolEvent
115+
{
116+
public string FileSetId { get; } = fileSetId;
117+
118+
public string ShareLink { get; } = shareLink;
119+
}
120+
121+
internal class FlashTransferRegisterFilesEventReq(string fileSetId, List<FlashTransferFile> files) : ProtocolEvent
122+
{
123+
public string FileSetId { get; } = fileSetId;
124+
125+
public List<FlashTransferFile> Files { get; } = files;
126+
}
127+
128+
internal class FlashTransferRegisterFilesEventResp : ProtocolEvent;
129+
130+
internal class FlashTransferQueryFileSetStatusEventReq(string fileSetId) : ProtocolEvent
131+
{
132+
public string FileSetId { get; } = fileSetId;
133+
}
134+
135+
internal class FlashTransferQueryFileSetStatusEventResp : ProtocolEvent;
136+
137+
internal class FlashTransferUploadAuthorizeEventReq(string fileSetId, FlashTransferFile file, uint scene) : ProtocolEvent
138+
{
139+
public string FileSetId { get; } = fileSetId;
140+
141+
public FlashTransferFile File { get; } = file;
142+
143+
public uint Scene { get; } = scene;
144+
}
145+
146+
internal class FlashTransferUploadAuthorizeEventResp(string uploadToken, string resourceKey, uint appId, string uploadHost, uint chunkSize, uint bindingStage, uint bindingField5, uint bindingField6) : ProtocolEvent
147+
{
148+
public string UploadToken { get; } = uploadToken;
149+
150+
public string ResourceKey { get; } = resourceKey;
151+
152+
public uint AppId { get; } = appId;
153+
154+
public string UploadHost { get; } = uploadHost;
155+
156+
public uint ChunkSize { get; } = chunkSize;
157+
158+
public uint BindingStage { get; } = bindingStage;
159+
160+
public uint BindingField5 { get; } = bindingField5;
161+
162+
public uint BindingField6 { get; } = bindingField6;
163+
}
164+
165+
internal class FlashTransferUploadCompleteEventReq(string fileSetId, FlashTransferFile file, string resourceKey, uint scene, uint bindingStage, uint bindingField5, uint bindingField6) : ProtocolEvent
166+
{
167+
public string FileSetId { get; } = fileSetId;
168+
169+
public FlashTransferFile File { get; } = file;
170+
171+
public string ResourceKey { get; } = resourceKey;
172+
173+
public uint Scene { get; } = scene;
174+
175+
public uint BindingStage { get; } = bindingStage;
176+
177+
public uint BindingField5 { get; } = bindingField5;
178+
179+
public uint BindingField6 { get; } = bindingField6;
180+
}
181+
182+
internal class FlashTransferUploadCompleteEventResp : ProtocolEvent;
183+
184+
internal class FlashTransferUpdateFileSetStatusEventReq(string fileSetId, uint status) : ProtocolEvent
185+
{
186+
public string FileSetId { get; } = fileSetId;
187+
188+
public uint Status { get; } = status;
189+
}
190+
191+
internal class FlashTransferUpdateFileSetStatusEventResp : ProtocolEvent;

0 commit comments

Comments
 (0)