11
11
using System . Security . Cryptography ;
12
12
using System . Text ;
13
13
using System . Text . Json ;
14
+ using System . Text . Json . Serialization ;
14
15
using System . Text . RegularExpressions ;
15
16
using System . Threading ;
16
17
using System . Threading . Tasks ;
@@ -30,6 +31,7 @@ public partial class ThreadsApi : IThreadsApi
30
31
31
32
private readonly string LoginUrl = $ "{ BaseApiUrl } /bloks/apps/com.bloks.www.bloks.caa.login.async.send_login_request/";
32
33
private readonly string PostUrl = $ "{ BaseApiUrl } /media/configure_text_only_post/";
34
+ private readonly string PostImageUrl = $ "{ BaseApiUrl } /media/configure_text_post_app_feed/";
33
35
34
36
public ThreadsApi ( HttpClient httpClient )
35
37
{
@@ -44,6 +46,39 @@ public async Task<UserIdResponse> GetUserIdFromUserNameAsync(string username, Ca
44
46
throw new ArgumentNullException ( nameof ( username ) ) ;
45
47
}
46
48
49
+ try
50
+ {
51
+ var userResult = await GetUserIdFromUserNameInternal ( username , cancellationToken ) ;
52
+
53
+ if ( int . TryParse ( userResult . UserId , out int value ) )
54
+ {
55
+ return new UserIdResponse
56
+ {
57
+ Token = userResult . LsdToken ,
58
+ UserId = value
59
+ } ;
60
+ }
61
+ }
62
+ catch ( UserNotFoundException )
63
+ {
64
+ var userResult = await GetUserIdFromUserNameInternal ( username , cancellationToken ) ;
65
+
66
+ if ( int . TryParse ( userResult . UserId , out int value ) )
67
+ {
68
+ return new UserIdResponse
69
+ {
70
+ Token = userResult . LsdToken ,
71
+ UserId = value
72
+ } ;
73
+ }
74
+ }
75
+
76
+
77
+ throw new UserNotFoundException ( username ) ;
78
+ }
79
+
80
+ private async Task < ( string UserId , string LsdToken ) > GetUserIdFromUserNameInternal ( string username , CancellationToken cancellationToken )
81
+ {
47
82
var request = new HttpRequestMessage ( HttpMethod . Get , $ "{ _url } @{ username } ") ;
48
83
GetDefaultHeaders ( null , request ) ;
49
84
@@ -74,16 +109,7 @@ public async Task<UserIdResponse> GetUserIdFromUserNameAsync(string username, Ca
74
109
throw new UserNotFoundException ( username ) ;
75
110
}
76
111
77
- if ( int . TryParse ( userID , out int value ) )
78
- {
79
- return new UserIdResponse
80
- {
81
- Token = lsdToken ,
82
- UserId = value
83
- } ;
84
- }
85
-
86
- throw new UserNotFoundException ( username ) ;
112
+ return ( userID , lsdToken ) ;
87
113
}
88
114
89
115
@@ -390,6 +416,65 @@ public async Task<string> PostAsync(string username, string message, string auth
390
416
return postData ? . media ? . id ;
391
417
}
392
418
419
+ public async Task < string > PostImageAsync ( string username , string message , string authToken , ImageUploadRequest image , CancellationToken cancellationToken = default )
420
+ {
421
+ if ( string . IsNullOrEmpty ( username ) )
422
+ {
423
+ throw new ArgumentNullException ( username ) ;
424
+ }
425
+ if ( string . IsNullOrWhiteSpace ( message ) )
426
+ {
427
+ throw new ArgumentNullException ( message ) ;
428
+ }
429
+ if ( string . IsNullOrWhiteSpace ( authToken ) )
430
+ {
431
+ throw new ArgumentNullException ( authToken ) ;
432
+ }
433
+
434
+ var userId = await GetUserIdFromUserNameAsync ( username , cancellationToken ) ;
435
+
436
+ var imageUploadResult = await UploadImageAsync ( image , authToken , cancellationToken ) ;
437
+
438
+ var data = new
439
+ {
440
+ text_post_app_info = new { reply_control = 0 } ,
441
+ timezone_offset = "3600" ,
442
+ source_type = '4' ,
443
+ _uid = userId . UserId ,
444
+ device_id = _deviceId ,
445
+ caption = message ,
446
+ upload_id = imageUploadResult . upload_id ,
447
+ device = new
448
+ {
449
+ manufacturer = "OnePlus" ,
450
+ model = "ONEPLUS+A3010" ,
451
+ os_version = 25 ,
452
+ os_release = "7.1.1" ,
453
+ } ,
454
+ publish_mode = "text_post" ,
455
+ scene_capture_type = string . Empty ,
456
+ } ;
457
+
458
+
459
+ var payload = $ "signed_body=SIGNATURE.{ JsonSerializer . Serialize ( data ) } ";
460
+
461
+ var request = new HttpRequestMessage ( HttpMethod . Post , new Uri ( PostImageUrl ) )
462
+ {
463
+ Content = new StringContent ( payload )
464
+ } ;
465
+ GetAppHeaders ( request , authToken ) ;
466
+ request . Content . Headers . Clear ( ) ;
467
+ request . Content . Headers . Add ( "Content-Type" , "application/x-www-form-urlencoded" ) ;
468
+ request . Content . Headers . Add ( "Response-Type" , "text" ) ;
469
+
470
+ var response = await _client . SendAsync ( request , cancellationToken ) . ConfigureAwait ( false ) ;
471
+
472
+ var stream = await response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) ;
473
+ var postData = await JsonSerializer . DeserializeAsync < PostResponse > ( stream ) . ConfigureAwait ( false ) ;
474
+
475
+ return postData ? . media ? . id ;
476
+ }
477
+
393
478
/// <inheritdoc/>
394
479
public Task < bool > FollowAsync ( int userId , string token , string authToken , CancellationToken cancellationToken = default )
395
480
{
@@ -423,6 +508,87 @@ private async Task<bool> FollowUnfollowInternal(string url, int userId, string t
423
508
return data ? . IsSuccess ?? false ;
424
509
}
425
510
511
+ private async Task < ImageUploadResponse > UploadImageAsync ( ImageUploadRequest image , string authToken , CancellationToken cancellationToken = default )
512
+ {
513
+ string uploadId = DateTime . Now . Ticks . ToString ( ) ;
514
+ string name = $ "{ uploadId } _0_{ new Random ( ) . Next ( 100000000 , 999999999 ) } ";
515
+ string url = $ "https://www.instagram.com/rupload_igphoto/{ name } ";
516
+
517
+ byte [ ] imageBytes ;
518
+ string mime_type ;
519
+
520
+ if ( ! string . IsNullOrEmpty ( image . Path ) )
521
+ {
522
+ bool isFilePath = ! image . Path . StartsWith ( "http" ) ;
523
+ if ( isFilePath )
524
+ {
525
+ imageBytes = await File . ReadAllBytesAsync ( image . Path ) . ConfigureAwait ( false ) ;
526
+ mime_type = "image/png" ?? "application/octet-stream" ;
527
+ }
528
+ else
529
+ {
530
+ HttpResponseMessage imageResponse = await _client . GetAsync ( image . Path ) . ConfigureAwait ( false ) ;
531
+ imageBytes = await imageResponse . Content . ReadAsByteArrayAsync ( ) ;
532
+ mime_type = imageResponse . Content . Headers . ContentType . MediaType ;
533
+ }
534
+ }
535
+ else
536
+ {
537
+ imageBytes = image . Content ;
538
+ mime_type = image . MimeType ?? "application/octet-stream" ;
539
+ }
540
+
541
+ Dictionary < string , string > x_instagram_rupload_params = new Dictionary < string , string >
542
+ {
543
+ { "upload_id" , uploadId } ,
544
+ { "media_type" , "1" } ,
545
+ { "sticker_burnin_params" , "[]" } ,
546
+ { "image_compression" , "{\" lib_name\" :\" moz\" ,\" lib_version\" :\" 3.1.m\" ,\" quality\" :\" 80\" }" } ,
547
+ { "xsharing_user_ids" , "[]" } ,
548
+ { "retry_context" , "{\" num_step_auto_retry\" :\" 0\" ,\" num_reupload\" :\" 0\" ,\" num_step_manual_retry\" :\" 0\" }" } ,
549
+ { "IG-FB-Xpost-entry-point-v2" , "feed" }
550
+ } ;
551
+
552
+ int contentLength = imageBytes . Length ;
553
+ Dictionary < string , string > imageHeaders = new Dictionary < string , string >
554
+ {
555
+ { "X_FB_PHOTO_WATERFALL_ID" , Guid . NewGuid ( ) . ToString ( ) } ,
556
+ { "X-Entity-Type" , mime_type ?? "image/jpeg" } ,
557
+ { "Offset" , "0" } ,
558
+ { "X-Instagram-Rupload-Params" , JsonSerializer . Serialize ( x_instagram_rupload_params ) } ,
559
+ { "X-Entity-Name" , name } ,
560
+ { "X-Entity-Length" , contentLength . ToString ( ) } ,
561
+ { "Accept-Encoding" , "gzip" }
562
+ } ;
563
+
564
+ Dictionary < string , string > contentHeaders = new Dictionary < string , string >
565
+ {
566
+ { "Content-Type" , "application/octet-stream" } ,
567
+ { "Content-Length" , contentLength . ToString ( ) } ,
568
+ } ;
569
+
570
+
571
+
572
+ var request = new HttpRequestMessage ( HttpMethod . Post , new Uri ( url ) ) ;
573
+
574
+ GetDefaultHeaders ( null , request , authToken ) ;
575
+ request . Content = new ByteArrayContent ( imageBytes ) ;
576
+ //request.Content.Headers.Clear();
577
+ foreach ( KeyValuePair < string , string > header in contentHeaders )
578
+ {
579
+ request . Content . Headers . Add ( header . Key , header . Value ) ;
580
+ }
581
+ foreach ( KeyValuePair < string , string > header in imageHeaders )
582
+ {
583
+ request . Headers . Add ( header . Key , header . Value ) ;
584
+ }
585
+
586
+ var response = await _client . SendAsync ( request , cancellationToken ) . ConfigureAwait ( false ) ;
587
+ var data = await response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) ;
588
+
589
+ return await JsonSerializer . DeserializeAsync < ImageUploadResponse > ( data , cancellationToken : cancellationToken ) . ConfigureAwait ( false ) ;
590
+ }
591
+
426
592
private void GetDefaultHeaders ( string token , HttpRequestMessage request , string authToken = default )
427
593
{
428
594
GetAppHeaders ( request , authToken ) ;
0 commit comments