Skip to content
This repository was archived by the owner on Oct 12, 2025. It is now read-only.
Merged
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
81 changes: 59 additions & 22 deletions Lagrange.Core/Internal/Context/HighwayContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ namespace Lagrange.Core.Internal.Context;
internal class HighwayContext : ContextBase, IDisposable
{
private const string Tag = nameof(HighwayContext);

private static readonly RuntimeTypeModel Serializer;

private uint _sequence;
private Uri? _uri;

private readonly Dictionary<Type, IHighwayUploader> _uploaders;
private readonly HttpClient _client;
private readonly int _chunkSize;
Expand All @@ -32,7 +32,7 @@ static HighwayContext()
Serializer = RuntimeTypeModel.Create();
Serializer.UseImplicitZeroDefaults = false;
}

public HighwayContext(ContextCollection collection, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, BotConfig config)
: base(collection, keystore, appInfo, device)
{
Expand All @@ -42,12 +42,12 @@ public HighwayContext(ContextCollection collection, BotKeystore keystore, BotApp
var attribute = impl.GetCustomAttribute<HighwayUploaderAttribute>();
if (attribute != null) _uploaders[attribute.Entity] = (IHighwayUploader)impl.CreateInstance(false);
}

var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
};

_client = new HttpClient(handler);
_client.DefaultRequestHeaders.Add("Accept-Encoding", "identity");
_client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2)");
Expand All @@ -65,8 +65,26 @@ public async Task UploadResources(MessageChain chain)
{
try
{
if (chain.IsGroup) await uploader.UploadGroup(Collection, chain, entity);
else await uploader.UploadPrivate(Collection, chain, entity);
if (chain.IsGroup) await uploader.UploadGroup(Collection, chain.GroupUin ?? 0, entity);
else await uploader.UploadPrivate(Collection, chain.FriendInfo?.Uid ?? "", entity);
}
catch
{
Collection.Log.LogFatal(Tag, $"Upload resources for {entity.GetType().Name} failed");
}
}
}
}

public async Task UploadGroupResources(MessageChain chain, uint uin)
{
foreach (var entity in chain)
{
if (_uploaders.TryGetValue(entity.GetType(), out var uploader))
{
try
{
await uploader.UploadGroup(Collection, uin, entity);
}
catch
{
Expand All @@ -76,6 +94,25 @@ public async Task UploadResources(MessageChain chain)
}
}

public async Task UploadPrivateResources(MessageChain chain, string uid)
{
foreach (var entity in chain)
{
if (_uploaders.TryGetValue(entity.GetType(), out var uploader))
{
try
{
await uploader.UploadPrivate(Collection, uid, entity);
}
catch
{
Collection.Log.LogFatal(Tag, $"Upload resources for {entity.GetType().Name} failed");
}
}
}
}


public async Task ManualUploadEntity(IMessageEntity entity)
{
if (_uploaders.TryGetValue(entity.GetType(), out var uploader))
Expand All @@ -85,8 +122,8 @@ public async Task ManualUploadEntity(IMessageEntity entity)
uint uin = Collection.Keystore.Uin;
string uid = Collection.Keystore.Uid ?? "";
var chain = new MessageChain(uin, uid, uid) { entity };
await uploader.UploadPrivate(Collection, chain, entity);

await uploader.UploadPrivate(Collection, chain.FriendInfo?.Uid ?? "", entity);
}
catch
{
Expand All @@ -103,7 +140,7 @@ public async Task<bool> UploadSrcByStreamAsync(int commonId, Stream data, byte[]
var result = (HighwayUrlEvent)highwayUrlEvent[0];
_uri = result.HighwayUrls[1][0];
}

bool success = true;
var upBlocks = new List<UpBlock>();

Expand All @@ -126,7 +163,7 @@ public async Task<bool> UploadSrcByStreamAsync(int commonId, Stream data, byte[]
var tasks = upBlocks.Select(x => SendUpBlockAsync(x, _uri)).ToArray();
var results = await Task.WhenAll(tasks);
success &= results.All(x => x);

upBlocks.Clear();
}
}
Expand Down Expand Up @@ -171,7 +208,7 @@ private async Task<bool> SendUpBlockAsync(UpBlock upBlock, Uri server)
};

bool isEnd = upBlock.Offset + (ulong)upBlock.Block.Length == upBlock.FileSize;
var payload = await SendPacketAsync(highwayHead, new BinaryPacket(upBlock.Block), server, isEnd);
var payload = await SendPacketAsync(highwayHead, new BinaryPacket(upBlock.Block), server, isEnd);
var (respHead, resp) = ParsePacket(payload);

Collection.Log.LogDebug(Tag, $"Highway Block Result: {respHead.ErrorCode} | {respHead.MsgSegHead?.RetCode} | {respHead.BytesRspExtendInfo?.Hex()} | {resp.ToArray().Hex()}");
Expand All @@ -183,15 +220,15 @@ private Task<BinaryPacket> SendPacketAsync(ReqDataHighwayHead head, BinaryPacket
{
using var stream = new MemoryStream();
Serializer.Serialize(stream, head);

var writer = new BinaryPacket()
.WriteByte(0x28) // packet start
.WriteInt((int)stream.Length)
.WriteInt((int)buffer.Length)
.WriteBytes(stream.ToArray())
.WritePacket(buffer)
.WriteByte(0x29); // packet end

return SendDataAsync(writer.ToArray(), server, end);
}

Expand All @@ -203,13 +240,13 @@ private static (RespDataHighwayHead, BinaryPacket) ParsePacket(BinaryPacket pack
int bodyLength = packet.ReadInt();
var head = Serializer.Deserialize<RespDataHighwayHead>(packet.ReadBytes(headLength).AsSpan());
var body = packet.ReadPacket(bodyLength);

if (packet.ReadByte() == 0x29) return (head, body);
}

throw new InvalidOperationException("Invalid packet");
}

private async Task<BinaryPacket> SendDataAsync(byte[] packet, Uri server, bool end)
{
var content = new ByteArrayContent(packet);
Expand All @@ -225,16 +262,16 @@ private async Task<BinaryPacket> SendDataAsync(byte[] packet, Uri server, bool e
var data = await response.Content.ReadAsByteArrayAsync();
return new BinaryPacket(data);
}

private record struct UpBlock(
int CommandId,
int CommandId,
uint Uin,
uint Sequence,
uint Sequence,
ulong FileSize,
ulong Offset,
ulong Offset,
byte[] Ticket,
byte[] FileMd5,
byte[] Block,
byte[] Block,
byte[]? ExtendInfo = null,
ulong Timestamp = 0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ public override async Task Outgoing(ProtocolEvent e)
await ResolveChainMetadata(chain);
await ResolveOutgoingChain(chain);
await Collection.Highway.UploadResources(chain);

foreach (var forward in chain.OfType<ForwardEntity>())
{
if (chain.IsGroup) await Collection.Highway.UploadGroupResources(forward.Chain, chain.GroupUin ?? 0);
else await Collection.Highway.UploadPrivateResources(forward.Chain, chain.FriendInfo?.Uid ?? "");
}
}
break;
}
Expand Down Expand Up @@ -447,6 +453,41 @@ private async Task ResolveIncomingChain(MessageChain chain)
image.ImageUrl = result.Url;
}

break;
}
case ForwardEntity forward:
{
if (chain is { GroupUin: not null })
{
var events = await Collection.Business.SendEvent(GetGroupMessageEvent.Create(
chain.GroupUin.Value,
forward.Sequence,
forward.Sequence
));

if (events.Count < 1) break;
if (events[0] is not GetGroupMessageEvent @event) break;
if (@event.ResultCode != 0) break;
if (@event.Chains.Count < 1) break;

forward.Chain = @event.Chains[0];
}
else
{
var events = await Collection.Business.SendEvent(GetC2cMessageEvent.Create(
chain.Uid ?? "",
forward.Sequence,
forward.Sequence
));

if (events.Count < 1) break;
if (events[0] is not GetC2cMessageEvent @event) break;
if (@event.ResultCode != 0) break;
if (@event.Chains.Count < 1) break;

forward.Chain = @event.Chains[0];
}

break;
}
}
Expand Down
6 changes: 3 additions & 3 deletions Lagrange.Core/Internal/Context/Uploader/IHighwayUploader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Lagrange.Core.Internal.Context.Uploader;

internal interface IHighwayUploader
{
public Task UploadPrivate(ContextCollection context, MessageChain chain, IMessageEntity entity);
public Task UploadGroup(ContextCollection context, MessageChain chain, IMessageEntity entity);
public Task UploadPrivate(ContextCollection context, string uid, IMessageEntity entity);

public Task UploadGroup(ContextCollection context, uint uin, IMessageEntity entity);
}
12 changes: 6 additions & 6 deletions Lagrange.Core/Internal/Context/Uploader/ImageUploader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ namespace Lagrange.Core.Internal.Context.Uploader;
[HighwayUploader(typeof(ImageEntity))]
internal class ImageUploader : IHighwayUploader
{
public async Task UploadPrivate(ContextCollection context, MessageChain chain, IMessageEntity entity)
public async Task UploadPrivate(ContextCollection context, string uid, IMessageEntity entity)
{
if (entity is ImageEntity { ImageStream: { } stream } image)
{
var uploadEvent = ImageUploadEvent.Create(image, chain.FriendInfo?.Uid ?? "");
var uploadEvent = ImageUploadEvent.Create(image, uid);
var uploadResult = await context.Business.SendEvent(uploadEvent);
var metaResult = (ImageUploadEvent)uploadResult[0];

Expand All @@ -26,18 +26,18 @@ public async Task UploadPrivate(ContextCollection context, MessageChain chain, I
throw new Exception();
}
}

image.MsgInfo = metaResult.MsgInfo; // directly constructed by Tencent's BDH Server
image.CompatImage = metaResult.Compat; // for legacy QQ
await image.ImageStream.Value.DisposeAsync();
}
}

public async Task UploadGroup(ContextCollection context, MessageChain chain, IMessageEntity entity)
public async Task UploadGroup(ContextCollection context, uint uin, IMessageEntity entity)
{
if (entity is ImageEntity { ImageStream: { } stream } image)
{
var uploadEvent = ImageGroupUploadEvent.Create(image, chain.GroupUin ?? 0);
var uploadEvent = ImageGroupUploadEvent.Create(image, uin);
var uploadResult = await context.Business.SendEvent(uploadEvent);
var metaResult = (ImageGroupUploadEvent)uploadResult[0];

Expand All @@ -51,7 +51,7 @@ public async Task UploadGroup(ContextCollection context, MessageChain chain, IMe
throw new Exception();
}
}

image.MsgInfo = metaResult.MsgInfo; // directly constructed by Tencent's BDH Server
image.CompatFace = metaResult.Compat; // for legacy QQ
await image.ImageStream.Value.DisposeAsync();
Expand Down
10 changes: 5 additions & 5 deletions Lagrange.Core/Internal/Context/Uploader/PttUploader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ namespace Lagrange.Core.Internal.Context.Uploader;
[HighwayUploader(typeof(RecordEntity))]
internal class PttUploader : IHighwayUploader
{
public async Task UploadPrivate(ContextCollection context, MessageChain chain, IMessageEntity entity)
public async Task UploadPrivate(ContextCollection context, string uid, IMessageEntity entity)
{
if (entity is RecordEntity { AudioStream: { } stream } record)
{
var uploadEvent = RecordUploadEvent.Create(record, chain.FriendInfo?.Uid ?? "");
var uploadEvent = RecordUploadEvent.Create(record, uid);
var uploadResult = await context.Business.SendEvent(uploadEvent);
var metaResult = (RecordUploadEvent)uploadResult[0];

Expand All @@ -33,11 +33,11 @@ public async Task UploadPrivate(ContextCollection context, MessageChain chain, I
}
}

public async Task UploadGroup(ContextCollection context, MessageChain chain, IMessageEntity entity)
public async Task UploadGroup(ContextCollection context, uint uin, IMessageEntity entity)
{
if (entity is RecordEntity { AudioStream: { } stream } record)
{
var uploadEvent = RecordGroupUploadEvent.Create(record, chain.GroupUin ?? 0);
var uploadEvent = RecordGroupUploadEvent.Create(record, uin);
var uploadResult = await context.Business.SendEvent(uploadEvent);
var metaResult = (RecordGroupUploadEvent)uploadResult[0];

Expand All @@ -51,7 +51,7 @@ public async Task UploadGroup(ContextCollection context, MessageChain chain, IMe
throw new Exception();
}
}

record.MsgInfo = metaResult.MsgInfo; // directly constructed by Tencent's BDH Server
record.Compat = metaResult.Compat; // for legacy QQ
await record.AudioStream.Value.DisposeAsync();
Expand Down
16 changes: 8 additions & 8 deletions Lagrange.Core/Internal/Context/Uploader/VideoUploader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ namespace Lagrange.Core.Internal.Context.Uploader;
[HighwayUploader(typeof(VideoEntity))]
internal class VideoUploader : IHighwayUploader
{
public async Task UploadPrivate(ContextCollection context, MessageChain chain, IMessageEntity entity)
public async Task UploadPrivate(ContextCollection context, string uid, IMessageEntity entity)
{
if (entity is VideoEntity { VideoStream: { } stream, ThumbnailStream: { } thumbnail } video)
{
var uploadEvent = VideoUploadEvent.Create(video, chain.FriendInfo?.Uid ?? "");
var uploadEvent = VideoUploadEvent.Create(video, uid);
var uploadResult = await context.Business.SendEvent(uploadEvent);
var metaResult = (VideoUploadEvent)uploadResult[0];

Expand All @@ -28,7 +28,7 @@ public async Task UploadPrivate(ContextCollection context, MessageChain chain, I
throw new Exception();
}
}

if (Common.GenerateExt(metaResult, metaResult.SubFiles[0]) is { } subExt)
{
var hash = metaResult.MsgInfo.MsgInfoBody[1].Index.Info.FileHash.UnHex();
Expand All @@ -48,18 +48,18 @@ public async Task UploadPrivate(ContextCollection context, MessageChain chain, I
}
}

public async Task UploadGroup(ContextCollection context, MessageChain chain, IMessageEntity entity)
public async Task UploadGroup(ContextCollection context, uint uin, IMessageEntity entity)
{
if (entity is VideoEntity { VideoStream: { } stream, ThumbnailStream: { } thumbnail } video)
{
var uploadEvent = VideoGroupUploadEvent.Create(video, chain.GroupUin ?? 0);
var uploadEvent = VideoGroupUploadEvent.Create(video, uin);
var uploadResult = await context.Business.SendEvent(uploadEvent);
var metaResult = (VideoGroupUploadEvent)uploadResult[0];

if (Common.GenerateExt(metaResult) is { } ext)
{
ext.Hash.FileSha1 = Common.CalculateStreamBytes(stream.Value);

var hash = metaResult.MsgInfo.MsgInfoBody[0].Index.Info.FileHash.UnHex();
bool hwSuccess = await context.Highway.UploadSrcByStreamAsync(1005, stream.Value, await Common.GetTicket(context), hash, ext.Serialize().ToArray());
if (!hwSuccess)
Expand All @@ -80,7 +80,7 @@ public async Task UploadGroup(ContextCollection context, MessageChain chain, IMe
throw new Exception();
}
}

video.MsgInfo = metaResult.MsgInfo; // directly constructed by Tencent's BDH Server
video.Compat = metaResult.Compat; // for legacy QQ
await stream.Value.DisposeAsync();
Expand Down
Loading