Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Lagrange.Core/Common/Interface/MessageExt.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Lagrange.Core.Internal.Logic;
using Lagrange.Core.Message;
using Lagrange.Core.Common.Response;

namespace Lagrange.Core.Common.Interface;

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

public static Task<BotFlashTransferUpload> UploadFlashTransfer(this BotContext context, IReadOnlyList<(Stream Stream, string? FileName)> files, string? title = null)
=> context.EventContext.GetLogic<OperationLogic>().UploadFlashTransfer(files, title);

public static Task RecallMessage(this BotContext context, BotMessage message)
=> context.EventContext.GetLogic<MessagingLogic>().RecallMessage(message);

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

public static Task GroupQuit(this BotContext context, long groupUin)
=> context.EventContext.GetLogic<OperationLogic>().GroupQuit(groupUin);
}
}
10 changes: 10 additions & 0 deletions Lagrange.Core/Common/Response/BotFlashTransferUpload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Lagrange.Core.Common.Response;

public class BotFlashTransferUpload(string fileSetId, List<string> fileIds, string shareLink)
{
public string FileSetId { get; } = fileSetId;

public List<string> FileIds { get; } = fileIds;

public string ShareLink { get; } = shareLink;
}
48 changes: 36 additions & 12 deletions Lagrange.Core/Internal/Context/FlashTransferContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Security.Cryptography;
using System.Security.Cryptography;
using Lagrange.Core.Internal.Packets.Service;
using Lagrange.Core.Utility;
using Lagrange.Core.Utility.Cryptography;
Expand All @@ -23,6 +23,28 @@ internal FlashTransferContext(BotContext botContext)
}

public async Task<bool> UploadFile(string uKey, uint appId, Stream bodyStream)
{
return await UploadFile(uKey, appId, 2, bodyStream, null, _url);
}

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)
{
var binding = new FlashTransferUploadFileBinding
{
FileSetId = fileSetId,
FileSetIdDup = fileSetId,
FileId = fileId,
Stage = bindingStage,
Field5 = bindingField5,
Field6 = bindingField6,
FileType = fileType,
FileIdDup = fileId,
};

return await UploadFile(uploadToken, appId, uploadIndex, bodyStream, binding, $"https://{uploadHost}/sliceupload");
}

private async Task<bool> UploadFile(string uKey, uint appId, uint uploadIndex, Stream bodyStream, FlashTransferUploadFileBinding? binding, string? url)
{
var sha1StateVs = new FlashTransferSha1StateV { State = [] };
var chunkCount = (uint)((bodyStream.Length + ChunkSize - 1) / ChunkSize);
Expand Down Expand Up @@ -61,33 +83,35 @@ public async Task<bool> UploadFile(string uKey, uint appId, Stream bodyStream)
var uploadBuffer = new byte[chunkLength];
await bodyStream.ReadExactlyAsync(uploadBuffer, 0, chunkLength);

var success = await UploadChunk(uKey, appId, (uint)chunkStart, sha1StateVs, uploadBuffer);
var success = await UploadChunk(uKey, appId, uploadIndex, (uint)chunkStart, sha1StateVs, uploadBuffer, binding, url);
if (!success) return false;
}

return true;
}

private async Task<bool> UploadChunk(string uKey, uint appId, uint start, FlashTransferSha1StateV chunkSha1S, byte[] body)
private async Task<bool> UploadChunk(string uKey, uint appId, uint uploadIndex, uint start, FlashTransferSha1StateV chunkSha1S, byte[] body, FlashTransferUploadFileBinding? binding, string? url)
{
byte[] chunkSha1 = SHA1.HashData(body);
var req = new FlashTransferUploadReq
{
FieId1 = 0,
FileId = 0,
AppId = appId,
FileId3 = 2,
UploadIndex = uploadIndex,
Body = new FlashTransferUploadBody
{
FieId1 = [],
FileId = [],
UKey = uKey,
Start = start,
End = (uint)(start + body.Length - 1),
Sha1 = SHA1.HashData(body),
Sha1StateV = chunkSha1S,
Body = body
Sha1 = chunkSha1,
Sha1StateV = binding == null ? chunkSha1S : new FlashTransferSha1StateV { State = [chunkSha1] },
Body = body,
FileBinding = binding
}
};
var payload = ProtoHelper.Serialize(req).ToArray();
var request = new HttpRequestMessage(HttpMethod.Post, _url)
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Headers =
{
Expand All @@ -104,10 +128,10 @@ private async Task<bool> UploadChunk(string uKey, uint appId, uint start, FlashT
if (resp.Status != "success")
{
_botContext.LogError(Tag,
$"FlashTransfer Upload chunk {start} failed: {resp.Status}");
$"FlashTransfer Upload chunk {start} failed: {resp.Status} appId: {appId}, uploadIndex: {uploadIndex}, keyLength: {uKey?.Length ?? 0}");
return false;
}

return true;
}
}
}
181 changes: 181 additions & 0 deletions Lagrange.Core/Internal/Events/System/FlashTransferEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using Lagrange.Core.Utility.Extension;

namespace Lagrange.Core.Internal.Events.System;

internal class FlashTransferFile(string fileId, uint index, string fileName, Stream stream)
{
public const uint DefaultFileType = 11;

public string FileId { get; } = fileId;

public uint Index { get; } = index;

public string FileName { get; } = fileName;

public uint FileType { get; } = ResolveFileType(fileName);

public FlashTransferFileCategory Category => ResolveCategory(FileType);

public Stream Stream { get; } = stream;

public byte[] FileSha1 { get; } = stream.Sha1();

public byte[] FileMd5 { get; } = stream.Md5();


private static uint ResolveFileType(string fileName)
{
return Path.GetExtension(fileName).ToLowerInvariant() switch
{
".mp3" or ".wav" or ".aac" or ".flac" => 1,
".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,
".doc" or ".docx" => 3,
".zip" or ".rar" or ".tar" or ".gz" => 4,
".apk" => 5,
".xls" or ".xlsx" => 6,
".ppt" or ".pptx" => 7,
".html" or ".htm" => 8,
".pdf" => 9,
".txt" => 10,
".psd" => 12,
".pt" or ".pth" or ".onnx" or ".model" or ".mlmodel" => 15,
".ttf" or ".otf" => 16,
".ipa" => 17,
".key" => 18,
".note" => 19,
".numbers" => 20,
".pages" => 21,
".sketch" => 22,
".dmg" => 23,
".pkg" => 24,
".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,
_ => DefaultFileType
};
}

private static FlashTransferFileCategory ResolveCategory(uint fileType)
{
return fileType switch
{
3 or 6 or 7 or 9 or 10 or 13 or 18 or 19 or 20 or 21 => FlashTransferFileCategory.Document,
26 => FlashTransferFileCategory.Image,
2 => FlashTransferFileCategory.Video,
4 => FlashTransferFileCategory.Archive,
25 => FlashTransferFileCategory.Folder,
_ => FlashTransferFileCategory.Other
};
}

private static string ResolveCategoryName(FlashTransferFileCategory category)
{
return category switch
{
FlashTransferFileCategory.Document => "文档",
FlashTransferFileCategory.Image => "图片",
FlashTransferFileCategory.Video => "视频",
FlashTransferFileCategory.Archive => "压缩包",
FlashTransferFileCategory.Folder => "文件夹",
_ => "其他"
};
}
}

internal enum FlashTransferFileCategory : uint
{
Document = 1,
Image = 2,
Video = 3,
Archive = 4,
Folder = 5,
Other = 6
}


internal class FlashTransferCreateFileSetEventReq(string title, string asciiTitle, List<FlashTransferFile> files) : ProtocolEvent
{
public string Title { get; } = title;

public string AsciiTitle { get; } = asciiTitle;

public List<FlashTransferFile> Files { get; } = files;

}

internal class FlashTransferCreateFileSetEventResp(string fileSetId, string shareLink) : ProtocolEvent
{
public string FileSetId { get; } = fileSetId;

public string ShareLink { get; } = shareLink;
}

internal class FlashTransferRegisterFilesEventReq(string fileSetId, List<FlashTransferFile> files) : ProtocolEvent
{
public string FileSetId { get; } = fileSetId;

public List<FlashTransferFile> Files { get; } = files;
}

internal class FlashTransferRegisterFilesEventResp : ProtocolEvent;

internal class FlashTransferQueryFileSetStatusEventReq(string fileSetId) : ProtocolEvent
{
public string FileSetId { get; } = fileSetId;
}

internal class FlashTransferQueryFileSetStatusEventResp : ProtocolEvent;

internal class FlashTransferUploadAuthorizeEventReq(string fileSetId, FlashTransferFile file, uint scene) : ProtocolEvent
{
public string FileSetId { get; } = fileSetId;

public FlashTransferFile File { get; } = file;

public uint Scene { get; } = scene;
}

internal class FlashTransferUploadAuthorizeEventResp(string uploadToken, string resourceKey, uint appId, string uploadHost, uint chunkSize, uint bindingStage, uint bindingField5, uint bindingField6) : ProtocolEvent
{
public string UploadToken { get; } = uploadToken;

public string ResourceKey { get; } = resourceKey;

public uint AppId { get; } = appId;

public string UploadHost { get; } = uploadHost;

public uint ChunkSize { get; } = chunkSize;

public uint BindingStage { get; } = bindingStage;

public uint BindingField5 { get; } = bindingField5;

public uint BindingField6 { get; } = bindingField6;
}

internal class FlashTransferUploadCompleteEventReq(string fileSetId, FlashTransferFile file, string resourceKey, uint scene, uint bindingStage, uint bindingField5, uint bindingField6) : ProtocolEvent
{
public string FileSetId { get; } = fileSetId;

public FlashTransferFile File { get; } = file;

public string ResourceKey { get; } = resourceKey;

public uint Scene { get; } = scene;

public uint BindingStage { get; } = bindingStage;

public uint BindingField5 { get; } = bindingField5;

public uint BindingField6 { get; } = bindingField6;
}

internal class FlashTransferUploadCompleteEventResp : ProtocolEvent;

internal class FlashTransferUpdateFileSetStatusEventReq(string fileSetId, uint status) : ProtocolEvent
{
public string FileSetId { get; } = fileSetId;

public uint Status { get; } = status;
}

internal class FlashTransferUpdateFileSetStatusEventResp : ProtocolEvent;
Loading
Loading