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