Skip to content

Commit b9df955

Browse files
committed
feat: 新增文件闪传
`/upload_flash_transfer`
1 parent c4b3efc commit b9df955

10 files changed

Lines changed: 1080 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: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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+
26+
private static uint ResolveFileType(string fileName)
27+
{
28+
return Path.GetExtension(fileName).ToLowerInvariant() switch
29+
{
30+
".mp3" or ".wav" or ".aac" or ".flac" => 1,
31+
".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,
32+
".doc" or ".docx" => 3,
33+
".zip" or ".rar" or ".tar" or ".gz" => 4,
34+
".apk" => 5,
35+
".xls" or ".xlsx" => 6,
36+
".ppt" or ".pptx" => 7,
37+
".html" or ".htm" => 8,
38+
".pdf" => 9,
39+
".txt" => 10,
40+
".psd" => 12,
41+
".pt" or ".pth" or ".onnx" or ".model" or ".mlmodel" => 15,
42+
".ttf" or ".otf" => 16,
43+
".ipa" => 17,
44+
".key" => 18,
45+
".note" => 19,
46+
".numbers" => 20,
47+
".pages" => 21,
48+
".sketch" => 22,
49+
".dmg" => 23,
50+
".pkg" => 24,
51+
".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,
52+
_ => DefaultFileType
53+
};
54+
}
55+
56+
private static FlashTransferFileCategory ResolveCategory(uint fileType)
57+
{
58+
return fileType switch
59+
{
60+
3 or 6 or 7 or 9 or 10 or 13 or 18 or 19 or 20 or 21 => FlashTransferFileCategory.Document,
61+
26 => FlashTransferFileCategory.Image,
62+
2 => FlashTransferFileCategory.Video,
63+
4 => FlashTransferFileCategory.Archive,
64+
25 => FlashTransferFileCategory.Folder,
65+
_ => FlashTransferFileCategory.Other
66+
};
67+
}
68+
69+
private static string ResolveCategoryName(FlashTransferFileCategory category)
70+
{
71+
return category switch
72+
{
73+
FlashTransferFileCategory.Document => "文档",
74+
FlashTransferFileCategory.Image => "图片",
75+
FlashTransferFileCategory.Video => "视频",
76+
FlashTransferFileCategory.Archive => "压缩包",
77+
FlashTransferFileCategory.Folder => "文件夹",
78+
_ => "其他"
79+
};
80+
}
81+
}
82+
83+
internal enum FlashTransferFileCategory : uint
84+
{
85+
Document = 1,
86+
Image = 2,
87+
Video = 3,
88+
Archive = 4,
89+
Folder = 5,
90+
Other = 6
91+
}
92+
93+
94+
internal class FlashTransferCreateFileSetEventReq(string title, string asciiTitle, List<FlashTransferFile> files) : ProtocolEvent
95+
{
96+
public string Title { get; } = title;
97+
98+
public string AsciiTitle { get; } = asciiTitle;
99+
100+
public List<FlashTransferFile> Files { get; } = files;
101+
102+
}
103+
104+
internal class FlashTransferCreateFileSetEventResp(string fileSetId, string shareLink) : ProtocolEvent
105+
{
106+
public string FileSetId { get; } = fileSetId;
107+
108+
public string ShareLink { get; } = shareLink;
109+
}
110+
111+
internal class FlashTransferRegisterFilesEventReq(string fileSetId, List<FlashTransferFile> files) : ProtocolEvent
112+
{
113+
public string FileSetId { get; } = fileSetId;
114+
115+
public List<FlashTransferFile> Files { get; } = files;
116+
}
117+
118+
internal class FlashTransferRegisterFilesEventResp : ProtocolEvent;
119+
120+
internal class FlashTransferQueryFileSetStatusEventReq(string fileSetId) : ProtocolEvent
121+
{
122+
public string FileSetId { get; } = fileSetId;
123+
}
124+
125+
internal class FlashTransferQueryFileSetStatusEventResp : ProtocolEvent;
126+
127+
internal class FlashTransferUploadAuthorizeEventReq(string fileSetId, FlashTransferFile file, uint scene) : ProtocolEvent
128+
{
129+
public string FileSetId { get; } = fileSetId;
130+
131+
public FlashTransferFile File { get; } = file;
132+
133+
public uint Scene { get; } = scene;
134+
}
135+
136+
internal class FlashTransferUploadAuthorizeEventResp(string uploadToken, string resourceKey, uint appId, string uploadHost, uint chunkSize, uint bindingStage, uint bindingField5, uint bindingField6) : ProtocolEvent
137+
{
138+
public string UploadToken { get; } = uploadToken;
139+
140+
public string ResourceKey { get; } = resourceKey;
141+
142+
public uint AppId { get; } = appId;
143+
144+
public string UploadHost { get; } = uploadHost;
145+
146+
public uint ChunkSize { get; } = chunkSize;
147+
148+
public uint BindingStage { get; } = bindingStage;
149+
150+
public uint BindingField5 { get; } = bindingField5;
151+
152+
public uint BindingField6 { get; } = bindingField6;
153+
}
154+
155+
internal class FlashTransferUploadCompleteEventReq(string fileSetId, FlashTransferFile file, string resourceKey, uint scene, uint bindingStage, uint bindingField5, uint bindingField6) : ProtocolEvent
156+
{
157+
public string FileSetId { get; } = fileSetId;
158+
159+
public FlashTransferFile File { get; } = file;
160+
161+
public string ResourceKey { get; } = resourceKey;
162+
163+
public uint Scene { get; } = scene;
164+
165+
public uint BindingStage { get; } = bindingStage;
166+
167+
public uint BindingField5 { get; } = bindingField5;
168+
169+
public uint BindingField6 { get; } = bindingField6;
170+
}
171+
172+
internal class FlashTransferUploadCompleteEventResp : ProtocolEvent;
173+
174+
internal class FlashTransferUpdateFileSetStatusEventReq(string fileSetId, uint status) : ProtocolEvent
175+
{
176+
public string FileSetId { get; } = fileSetId;
177+
178+
public uint Status { get; } = status;
179+
}
180+
181+
internal class FlashTransferUpdateFileSetStatusEventResp : ProtocolEvent;

0 commit comments

Comments
 (0)