-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathFlashTransferContext.cs
More file actions
113 lines (99 loc) · 3.74 KB
/
FlashTransferContext.cs
File metadata and controls
113 lines (99 loc) · 3.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
using System.Security.Cryptography;
using Lagrange.Core.Internal.Packets.Service;
using Lagrange.Core.Utility;
using Lagrange.Core.Utility.Cryptography;
using Lagrange.Core.Utility.Extension;
namespace Lagrange.Core.Internal.Context;
public class FlashTransferContext
{
private const string Tag = nameof(FlashTransferContext);
private readonly BotContext _botContext;
private readonly HttpClient _client;
private readonly string? _url;
private const uint ChunkSize = 1024 * 1024;
internal FlashTransferContext(BotContext botContext)
{
_botContext = botContext;
_client = new HttpClient();
_client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
_url = "https://multimedia.qfile.qq.com/sliceupload";
}
public async Task<bool> UploadFile(string uKey, Stream bodyStream)
{
var sha1StateVs = new FlashTransferSha1StateV { State = [] };
var chunkCount = (uint)((bodyStream.Length + ChunkSize - 1) / ChunkSize);
var sha1Stream = new Sha1Stream();
for (uint i = 0; i < chunkCount; i++)
{
if (i != chunkCount - 1)
{
var accLength = (int)((i + 1) * ChunkSize);
var accBuffer = new byte[accLength];
bodyStream.Position = 0;
await bodyStream.ReadExactlyAsync(accBuffer, 0, accLength);
var accSpan = accBuffer.AsSpan();
var digest = new byte[20];
sha1Stream.Update(accSpan);
sha1Stream.Hash(digest, false);
sha1Stream.Reset();
sha1StateVs.State.Add(digest.ToArray());
}
else
{
bodyStream.Position = 0;
sha1StateVs.State.Add(bodyStream.Sha1());
}
}
for (uint i = 0; i < chunkCount; i++)
{
var chunkStart = (long)(i * ChunkSize);
var chunkLength = (int)Math.Min(ChunkSize, bodyStream.Length - chunkStart);
bodyStream.Position = chunkStart;
var uploadBuffer = new byte[chunkLength];
await bodyStream.ReadExactlyAsync(uploadBuffer, 0, chunkLength);
var success = await UploadChunk(uKey, (uint)chunkStart, sha1StateVs, uploadBuffer);
if (!success) return false;
}
return true;
}
private async Task<bool> UploadChunk(string uKey, uint start, FlashTransferSha1StateV chunkSha1S, byte[] body)
{
var req = new FlashTransferUploadReq
{
FieId1 = 0,
AppId = 1407,
FileId3 = 2,
Body = new FlashTransferUploadBody
{
FieId1 = [],
UKey = uKey,
Start = start,
End = (uint)(start + body.Length - 1),
Sha1 = SHA1.HashData(body),
Sha1StateV = chunkSha1S,
Body = body
}
};
var payload = ProtoHelper.Serialize(req).ToArray();
var request = new HttpRequestMessage(HttpMethod.Post, _url)
{
Headers =
{
{ "Accept", "*/*" },
{ "Expect", "100-continue" },
{ "Connection", "Keep-Alive" }
},
Content = new ByteArrayContent(payload)
};
var response = await _client.SendAsync(request);
var responseBytes = await response.Content.ReadAsByteArrayAsync();
var resp = ProtoHelper.Deserialize<FlashTransferUploadResp>(responseBytes);
if (resp.Status != "success")
{
_botContext.LogError(Tag,
$"FlashTransfer Upload chunk {start} failed: {resp.Status}");
return false;
}
return true;
}
}