diff --git a/Lagrange.Core/Common/Interface/Api/GroupExt.cs b/Lagrange.Core/Common/Interface/Api/GroupExt.cs index ac8f5c815..b1d0e5349 100644 --- a/Lagrange.Core/Common/Interface/Api/GroupExt.cs +++ b/Lagrange.Core/Common/Interface/Api/GroupExt.cs @@ -1,5 +1,6 @@ using Lagrange.Core.Common.Entity; using Lagrange.Core.Event.EventArg; +using Lagrange.Core.Message.Entity; namespace Lagrange.Core.Common.Interface.Api; @@ -77,6 +78,9 @@ public static Task FetchGroupFSDownload(this BotContext bot, uint groupU public static Task GroupFSMove(this BotContext bot, uint groupUin, string fileId, string parentDirectory, string targetDirectory) => bot.ContextCollection.Business.OperationLogic.GroupFSMove(groupUin, fileId, parentDirectory, targetDirectory); + + public static Task GroupFSUpload(this BotContext bot, uint groupUin, FileEntity fileEntity) + => bot.ContextCollection.Business.OperationLogic.GroupFSUpload(groupUin, fileEntity); #endregion } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs index d13446aed..cd4cb0f55 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs @@ -1,9 +1,11 @@ using Lagrange.Core.Common.Entity; using Lagrange.Core.Internal.Context.Attributes; +using Lagrange.Core.Internal.Context.Uploader; using Lagrange.Core.Internal.Event.Action; using Lagrange.Core.Internal.Event.Message; using Lagrange.Core.Internal.Event.System; using Lagrange.Core.Message; +using Lagrange.Core.Message.Entity; namespace Lagrange.Core.Internal.Context.Logic.Implementation; @@ -144,6 +146,18 @@ public async Task GroupFSMove(uint groupUin, string fileId, string parentD return events.Count != 0 && ((GroupFSMoveEvent)events[0]).ResultCode == 0; } + public Task GroupFSUpload(uint groupUin, FileEntity fileEntity) + { + try + { + return FileUploader.UploadGroup(Collection, MessageBuilder.Group(groupUin).Build(), fileEntity); + } + catch + { + return Task.FromResult(false); + } + } + public async Task RecallGroupMessage(uint groupUin, MessageResult result) { if (result.Sequence == null) return false; diff --git a/Lagrange.Core/Internal/Context/Uploader/FileUploader.cs b/Lagrange.Core/Internal/Context/Uploader/FileUploader.cs index 2b5f1f56a..54aface3f 100644 --- a/Lagrange.Core/Internal/Context/Uploader/FileUploader.cs +++ b/Lagrange.Core/Internal/Context/Uploader/FileUploader.cs @@ -1,23 +1,90 @@ using Lagrange.Core.Internal.Event.Message; +using Lagrange.Core.Internal.Event.System; +using Lagrange.Core.Internal.Packets.Service.Highway; using Lagrange.Core.Message; using Lagrange.Core.Message.Entity; +using Lagrange.Core.Utility.Extension; namespace Lagrange.Core.Internal.Context.Uploader; -[HighwayUploader(typeof(FileEntity))] -internal class FileUploader : IHighwayUploader +/// +/// This FileUploader should be called manually +/// +internal static class FileUploader { - public Task UploadPrivate(ContextCollection context, MessageChain chain, IMessageEntity entity) + public static Task UploadPrivate(ContextCollection context, MessageChain chain, IMessageEntity entity) { throw new NotImplementedException(); } - public async Task UploadGroup(ContextCollection context, MessageChain chain, IMessageEntity entity) + public static async Task UploadGroup(ContextCollection context, MessageChain chain, IMessageEntity entity) { - if (entity is FileEntity { FileStream: not null } file) + if (entity is not FileEntity { FileStream: not null } file) return false; + + var uploadEvent = GroupFSUploadEvent.Create(chain.GroupUin ?? 0, file); + var result = await context.Business.SendEvent(uploadEvent); + var uploadResp = (GroupFSUploadEvent)result[0]; + + var hwUrlEvent = HighwayUrlEvent.Create(); + var highwayUrlResult = await context.Business.SendEvent(hwUrlEvent); + var ticketResult = (HighwayUrlEvent)highwayUrlResult[0]; + + var ext = new FileUploadExt { - var uploadEvent = GroupFSUploadEvent.Create(chain.GroupUin ?? 0, file); - var result = await context.Business.SendEvent(uploadEvent); - } + Unknown1 = 100, + Unknown2 = 1, + Entry = new FileUploadEntry + { + BusiBuff = new ExcitingBusiInfo + { + SenderUin = context.Keystore.Uin, + ReceiverUin = chain.GroupUin ?? 0, + GroupCode = chain.GroupUin ?? 0 + }, + FileEntry = new ExcitingFileEntry + { + FileSize = file.FileStream.Length, + Md5 = file.FileMd5, + CheckKey = uploadResp.CheckKey, + Md5S2 = file.FileMd5, + FileId = uploadResp.FileId, + UploadKey = uploadResp.UploadKey + }, + ClientInfo = new ExcitingClientInfo + { + ClientType = 3, + AppId = "100", + TerminalType = 3, + ClientVer = "1.1.1", + Unknown = 4 + }, + FileNameInfo = new ExcitingFileNameInfo + { + FileName = file.FileName + }, + Host = new ExcitingHostConfig + { + Hosts = new List + { + new() + { + Url = new ExcitingUrlInfo + { + Unknown = 1, + Host = uploadResp.Ip + }, + Port = uploadResp.Port + } + } + } + } + }; + + bool hwSuccess = await context.Highway.UploadSrcByStreamAsync(71, file.FileStream, ticketResult.SigSession, file.FileMd5, ext.Serialize().ToArray()); + if (!hwSuccess) return false; + + var sendEvent = GroupSendFileEvent.Create(chain.GroupUin ?? 0, uploadResp.FileId); + var sendResult = await context.Business.SendEvent(sendEvent); + return sendResult.Count != 0 && ((GroupSendFileEvent)sendResult[0]).ResultCode == 0; } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/Message/GroupFSUploadEvent.cs b/Lagrange.Core/Internal/Event/Message/GroupFSUploadEvent.cs index bf7602d87..ae51b4ecc 100644 --- a/Lagrange.Core/Internal/Event/Message/GroupFSUploadEvent.cs +++ b/Lagrange.Core/Internal/Event/Message/GroupFSUploadEvent.cs @@ -7,7 +7,18 @@ namespace Lagrange.Core.Internal.Event.Message; internal class GroupFSUploadEvent : ProtocolEvent { public uint GroupUin { get; } + public FileEntity Entity { get; } + + public string FileId { get; } + + public byte[] UploadKey { get; } + + public byte[] CheckKey { get; } + + public string Ip { get; } + + public uint Port { get; } private GroupFSUploadEvent(uint groupUin, FileEntity entity) : base(true) { @@ -15,9 +26,17 @@ private GroupFSUploadEvent(uint groupUin, FileEntity entity) : base(true) Entity = entity; } - private GroupFSUploadEvent(int resultCode) : base(resultCode) + private GroupFSUploadEvent(int resultCode, string fileId, byte[] uploadKey, byte[] checkKey, string ip, uint port) : base(resultCode) { + FileId = fileId; + UploadKey = uploadKey; + CheckKey = checkKey; + Ip = ip; + Port = port; } public static GroupFSUploadEvent Create(uint groupUin, FileEntity entity) => new(groupUin, entity); + + public static GroupFSUploadEvent Result(int resultCode, string fileId, byte[] uploadKey, byte[] checkKey, string ip, uint port) + => new(resultCode, fileId, uploadKey, checkKey, ip, port); } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/Message/GroupSendFileEvent.cs b/Lagrange.Core/Internal/Event/Message/GroupSendFileEvent.cs new file mode 100644 index 000000000..97f5024f7 --- /dev/null +++ b/Lagrange.Core/Internal/Event/Message/GroupSendFileEvent.cs @@ -0,0 +1,20 @@ +namespace Lagrange.Core.Internal.Event.Message; + +internal class GroupSendFileEvent : ProtocolEvent +{ + public uint GroupUin { get; } + + public string FileKey { get; } = string.Empty; + + private GroupSendFileEvent(uint groupUin, string fileKey) : base(true) + { + GroupUin = groupUin; + FileKey = fileKey; + } + + private GroupSendFileEvent(int resultCode) : base(resultCode) { } + + public static GroupSendFileEvent Create(uint groupUin, string fileKey) => new(groupUin, fileKey); + + public static GroupSendFileEvent Result(int resultCode) => new(resultCode); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Highway/FileUploadExt.cs b/Lagrange.Core/Internal/Packets/Service/Highway/FileUploadExt.cs new file mode 100644 index 000000000..5985b75d5 --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Highway/FileUploadExt.cs @@ -0,0 +1,103 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Service.Highway; + +#pragma warning disable CS8618 + +[ProtoContract] +internal class FileUploadExt +{ + [ProtoMember(1)] public int Unknown1 { get; set; } + + [ProtoMember(2)] public int Unknown2 { get; set; } + + [ProtoMember(3)] public int Unknown3 { get; set; } + + [ProtoMember(100)] public FileUploadEntry Entry { get; set; } + + [ProtoMember(200)] public int Unknown200 { get; set; } +} + +[ProtoContract] +internal class FileUploadEntry +{ + [ProtoMember(100)] public ExcitingBusiInfo BusiBuff { get; set; } + + [ProtoMember(200)] public ExcitingFileEntry FileEntry { get; set; } + + [ProtoMember(300)] public ExcitingClientInfo ClientInfo { get; set; } + + [ProtoMember(400)] public ExcitingFileNameInfo FileNameInfo { get; set; } + + [ProtoMember(500)] public ExcitingHostConfig Host { get; set; } +} + +[ProtoContract] +internal class ExcitingBusiInfo +{ + [ProtoMember(1)] public int BusId { get; set; } + + [ProtoMember(100)] public long SenderUin { get; set; } + + [ProtoMember(200)] public long ReceiverUin { get; set; } + + [ProtoMember(400)] public long GroupCode { get; set; } +} + +[ProtoContract] +internal class ExcitingFileEntry +{ + [ProtoMember(100)] public long FileSize { get; set; } + + [ProtoMember(200)] public byte[] Md5 { get; set; } + + [ProtoMember(300)] public byte[] CheckKey { get; set; } + + [ProtoMember(400)] public byte[] Md5S2 { get; set; } + + [ProtoMember(600)] public string FileId { get; set; } + + [ProtoMember(700)] public byte[] UploadKey { get; set; } +} + +[ProtoContract] +internal class ExcitingClientInfo +{ + [ProtoMember(100)] public int ClientType { get; set; } + + [ProtoMember(200)] public string AppId { get; set; } + + [ProtoMember(300)] public int TerminalType { get; set; } + + [ProtoMember(400)] public string ClientVer { get; set; } + + [ProtoMember(600)] public int Unknown { get; set; } +} + +[ProtoContract] +internal class ExcitingFileNameInfo +{ + [ProtoMember(100)] public string FileName { get; set; } +} + +[ProtoContract] +internal class ExcitingHostConfig +{ + [ProtoMember(200)] public List Hosts { get; set; } +} + +[ProtoContract] +internal class ExcitingHostInfo +{ + [ProtoMember(1)] public ExcitingUrlInfo Url { get; set; } + + [ProtoMember(2)] public uint Port { get; set; } +} + +[ProtoContract] +internal class ExcitingUrlInfo +{ + [ProtoMember(1)] public int Unknown { get; set; } + + [ProtoMember(2)] public string Host { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D9_4.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D9_4.cs new file mode 100644 index 000000000..87369ee7b --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x6D9_4.cs @@ -0,0 +1,40 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request; + +#pragma warning disable CS8618 +// ReSharper disable InconsistentNaming + +/// +/// Group Send File +/// +[ProtoContract] +[OidbSvcTrpcTcp(0x6D9, 4)] +internal class OidbSvcTrpcTcp0x6D9_4 +{ + [ProtoMember(5)] public OidbSvcTrpcTcp0x6D9_4Body Body { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x6D9_4Body +{ + [ProtoMember(1)] public uint GroupUin { get; set; } + + [ProtoMember(2)] public uint Type { get; set; } // 2 + + [ProtoMember(3)] public OidbSvcTrpcTcp0x6D9_4Info Info { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x6D9_4Info +{ + [ProtoMember(1)] public uint BusiType { get; set; } // 102 + + [ProtoMember(2)] public string FileId { get; set; } + + [ProtoMember(3)] public uint Field3 { get; set; } // random + + [ProtoMember(4)] public string? Field4 { get; set; } // null + + [ProtoMember(5)] public bool Field5 { get; set; } // true +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D6Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D6Response.cs index 0171bfaae..b9a9d56f4 100644 --- a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D6Response.cs +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D6Response.cs @@ -8,5 +8,7 @@ namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response; [ProtoContract] internal class OidbSvcTrpcTcp0x6D6Response { + [ProtoMember(1)] public OidbSvcTrpcTcp0x6D6_0Response Upload { get; set; } + [ProtoMember(3)] public OidbSvcTrpcTcp0x6D6_2Response Download { get; set; } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D6_0Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D6_0Response.cs new file mode 100644 index 000000000..7bafd6b33 --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x6D6_0Response.cs @@ -0,0 +1,36 @@ +using ProtoBuf; + +#pragma warning disable CS8618 +// Resharper disable InconsistentNaming + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response; + +[ProtoContract] +internal class OidbSvcTrpcTcp0x6D6_0Response +{ + [ProtoMember(1)] public int RetCode { get; set; } + + [ProtoMember(2)] public string RetMsg { get; set; } + + [ProtoMember(3)] public string ClientWording { get; set; } + + [ProtoMember(4)] public string UploadIp { get; set; } + + [ProtoMember(5)] public string ServerDns { get; set; } + + [ProtoMember(6)] public int BusId { get; set; } + + [ProtoMember(7)] public string FileId { get; set; } + + [ProtoMember(8)] public byte[] CheckKey { get; set; } + + [ProtoMember(9)] public byte[] FileKey { get; set; } + + [ProtoMember(10)] public bool BoolFileExist { get; set; } + + [ProtoMember(12)] public List UploadIpLanV4 { get; set; } + + [ProtoMember(13)] public List UploadIpLanV6 { get; set; } + + [ProtoMember(14)] public uint UploadPort { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/Message/GroupFSUploadService.cs b/Lagrange.Core/Internal/Service/Message/GroupFSUploadService.cs index 1c8d866ee..be58269ae 100644 --- a/Lagrange.Core/Internal/Service/Message/GroupFSUploadService.cs +++ b/Lagrange.Core/Internal/Service/Message/GroupFSUploadService.cs @@ -3,12 +3,14 @@ using Lagrange.Core.Internal.Event.Message; using Lagrange.Core.Internal.Packets.Service.Oidb; using Lagrange.Core.Internal.Packets.Service.Oidb.Request; +using Lagrange.Core.Internal.Packets.Service.Oidb.Response; using Lagrange.Core.Utility.Binary; using Lagrange.Core.Utility.Extension; using ProtoBuf; namespace Lagrange.Core.Internal.Service.Message; +[EventSubscribe(typeof(GroupFSUploadEvent))] [Service("OidbSvcTrpcTcp.0x6d6_0")] internal class GroupFSUploadService : BaseService { @@ -27,7 +29,7 @@ protected override bool Build(GroupFSUploadEvent input, BotKeystore keystore, Bo FileName = input.Entity.FileName, LocalDirectory = $"/{input.Entity.FileName}", FileSize = input.Entity.FileSize, - FileSha1 = input.Entity.FileStream!.Sha1().UnHex(), + FileSha1 = input.Entity.FileStream?.Sha1().UnHex() ?? Array.Empty(), FileSha3 = Array.Empty(), FileMd5 = input.Entity.FileMd5, Field15 = true @@ -42,8 +44,11 @@ protected override bool Build(GroupFSUploadEvent input, BotKeystore keystore, Bo protected override bool Parse(byte[] input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, out GroupFSUploadEvent output, out List? extraEvents) { - Console.WriteLine(input.Hex()); + var payload = Serializer.Deserialize>(input.AsSpan()); + var upload = payload.Body.Upload; - return base.Parse(input, keystore, appInfo, device, out output, out extraEvents); + output = GroupFSUploadEvent.Result(upload.RetCode, upload.FileId, upload.FileKey, upload.CheckKey, upload.UploadIp, upload.UploadPort); + extraEvents = null; + return true; } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/Message/GroupSendFileService.cs b/Lagrange.Core/Internal/Service/Message/GroupSendFileService.cs new file mode 100644 index 000000000..8f0a0d162 --- /dev/null +++ b/Lagrange.Core/Internal/Service/Message/GroupSendFileService.cs @@ -0,0 +1,49 @@ +using Lagrange.Core.Common; +using Lagrange.Core.Internal.Event; +using Lagrange.Core.Internal.Event.Message; +using Lagrange.Core.Internal.Packets.Service.Oidb; +using Lagrange.Core.Internal.Packets.Service.Oidb.Request; +using Lagrange.Core.Utility.Binary; +using Lagrange.Core.Utility.Extension; +using ProtoBuf; + +namespace Lagrange.Core.Internal.Service.Message; + +[EventSubscribe(typeof(GroupSendFileEvent))] +[Service("OidbSvcTrpcTcp.0x6d9_4")] +internal class GroupSendFileService : BaseService +{ + protected override bool Build(GroupSendFileEvent input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out BinaryPacket output, out List? extraPackets) + { + var packet = new OidbSvcTrpcTcpBase(new OidbSvcTrpcTcp0x6D9_4 + { + Body = new OidbSvcTrpcTcp0x6D9_4Body + { + GroupUin = input.GroupUin, + Type = 2, + Info = new OidbSvcTrpcTcp0x6D9_4Info + { + BusiType = 102, + FileId = input.FileKey, + Field3 = (uint)Random.Shared.Next(), + Field5 = true + } + } + }); + + output = packet.Serialize(); + extraPackets = null; + return true; + } + + protected override bool Parse(byte[] input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out GroupSendFileEvent output, out List? extraEvents) + { + var payload = Serializer.Deserialize>(input.AsSpan()); + + output = GroupSendFileEvent.Result((int)payload.ErrorCode); + extraEvents = null; + return true; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Message/Entity/FileEntity.cs b/Lagrange.Core/Message/Entity/FileEntity.cs index 6a2f6a441..71d27ecdb 100644 --- a/Lagrange.Core/Message/Entity/FileEntity.cs +++ b/Lagrange.Core/Message/Entity/FileEntity.cs @@ -26,26 +26,39 @@ public class FileEntity : IMessageEntity internal Stream? FileStream { get; set; } + internal byte[] FileSha1 { get; set; } + public FileEntity() { FileName = ""; FileMd5 = Array.Empty(); + FileSha1 = Array.Empty(); } + /// + /// This entity could not be directly sent via , + /// it should be sent via + /// public FileEntity(string path) { FileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); FileMd5 = FileStream.Md5().UnHex(); FileSize = FileStream.Length; FileName = Path.GetFileName(path); + FileSha1 = FileStream.Sha1().UnHex(); } + /// + /// This entity could not be directly sent via , + /// it should be sent via + /// public FileEntity(byte[] payload, string fileName) { FileStream = new MemoryStream(payload); FileMd5 = payload.Md5().UnHex(); FileSize = payload.Length; FileName = fileName; + FileSha1 = FileStream.Sha1().UnHex(); } internal FileEntity(long fileSize, string fileName, byte[] fileMd5, string fileUuid, string fileHash) @@ -55,6 +68,7 @@ internal FileEntity(long fileSize, string fileName, byte[] fileMd5, string fileU FileMd5 = fileMd5; FileUuid = fileUuid; FileHash = fileHash; + FileSha1 = Array.Empty(); } IEnumerable IMessageEntity.PackElement() => Array.Empty(); diff --git a/Lagrange.Core/Message/MessageBuilder.cs b/Lagrange.Core/Message/MessageBuilder.cs index 677084d9c..019b968eb 100644 --- a/Lagrange.Core/Message/MessageBuilder.cs +++ b/Lagrange.Core/Message/MessageBuilder.cs @@ -175,22 +175,6 @@ public MessageBuilder LightApp(string payload) return this; } - - public MessageBuilder File(byte[] file, string fileName) - { - var fileEntity = new FileEntity(file, fileName); - _chain.Add(fileEntity); - - return this; - } - - public MessageBuilder File(string filePath) - { - var fileEntity = new FileEntity(filePath); - _chain.Add(fileEntity); - - return this; - } public MessageBuilder LongMsg(string resId) {