From a7bba0219f4ecf5d29f8f5a3f9a634fb6dea90cd Mon Sep 17 00:00:00 2001 From: SlavaObrezkov Date: Mon, 11 Aug 2025 16:30:15 +0300 Subject: [PATCH 1/6] CCL-6504 Add Temu CustomTask support with integration test --- .../IntegrationTests.cs | 47 +++++++++++++++++++ .../ObjectGen.cs | 39 +++++++++++++++ .../Sut.cs | 4 ++ ...CapMonsterCloudClient_GetResultTimeouts.cs | 9 ++++ .../Requests/TemuCustomTaskRequest.cs | 41 ++++++++++++++++ 5 files changed, 140 insertions(+) create mode 100644 CapMonsterCloud.Client/Requests/TemuCustomTaskRequest.cs diff --git a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs index 7efb922..2d07a25 100644 --- a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs +++ b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs @@ -1071,5 +1071,52 @@ public async Task Binance_ShouldSolve() sut.GetActualRequests().Should().BeEquivalentTo(expectedRequests); actual.Should().BeEquivalentTo(expectedResult); } + + [Test] + public async Task Temu_ShouldSolve() + { + var clientKey = Gen.RandomString(); + var taskId = Gen.RandomInt(); + + var captchaRequest = ObjectGen.TemuTask.CreateTask(); + var expectedResult = ObjectGen.TemuTask.CreateSolution(); + + var expectedRequests = new List<(RequestType Type, string ExpectedRequest)> + { + ( + Type: RequestType.CreateTask, + ExpectedRequest: JsonConvert.SerializeObject(new + { clientKey = clientKey, task = captchaRequest, softId = 53 }) + ), + ( + Type: RequestType.GetTaskResult, + ExpectedRequest: JsonConvert.SerializeObject(new { clientKey = clientKey, taskId = taskId }) + ), + }; + + var captchaResults = new List + { + new { taskId = taskId, errorId = 0, errorCode = (string)null! }, + new + { + status = "ready", + solution = new + { + domains = expectedResult.Solution.Domains + }, + errorId = 0, + errorCode = (string)null! + } + }; + + var sut = new Sut(clientKey); + sut.SetupHttpServer(captchaResults); + + var actual = await sut.SolveAsync(captchaRequest); + + sut.GetActualRequests().Should().BeEquivalentTo(expectedRequests); + actual.Should().BeEquivalentTo(expectedResult); + } + } } \ No newline at end of file diff --git a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs index 652ca8c..793baf5 100644 --- a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs +++ b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs @@ -546,5 +546,44 @@ public static CaptchaResult CreateSolution() }; } } + + public static class TemuTask + { + public static TemuCustomTaskRequest CreateTask() + { + return new TemuCustomTaskRequest(Gen.RandomString()) + { + WebsiteUrl = Gen.RandomUri().ToString(), + UserAgent = Gen.UserAgent(), + Proxy = new ProxyContainer(Gen.RandomString(), Gen.RandomInt(0, 65535), Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) + }; + } + + public static CaptchaResult CreateSolution() + { + return new CaptchaResult + { + Error = null, + Solution = new CustomTaskResponse + { + Domains = new Dictionary + { + { + "www.temu.com", + new CustomTaskResponse.DomainInfo + { + Cookies = new Dictionary + { + { "verifyAuthToken", Gen.RandomString() }, + { "api_uid", Gen.RandomString() } + } + } + } + } + } + }; + } + + } } } \ No newline at end of file diff --git a/CapMonsterCloud.Client.IntegrationTests/Sut.cs b/CapMonsterCloud.Client.IntegrationTests/Sut.cs index 57cc413..9a12bde 100644 --- a/CapMonsterCloud.Client.IntegrationTests/Sut.cs +++ b/CapMonsterCloud.Client.IntegrationTests/Sut.cs @@ -98,6 +98,10 @@ public async Task> SolveAsync( public async Task> SolveAsync( RecognitionComplexImageTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task> SolveAsync( + TemuCustomTaskRequest request) => await _cloudClient.SolveAsync(request); + + public async Task GetBalanceAsync() { return await _cloudClient.GetBalanceAsync(); diff --git a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs index 2cd856c..df4508c 100644 --- a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs +++ b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs @@ -183,6 +183,15 @@ private static GetResultTimeouts GetTimeouts(Type type) Timeout = TimeSpan.FromSeconds(15) } }, + { + typeof(TemuCustomTaskRequest), + new GetResultTimeouts + { + FirstRequestDelay = TimeSpan.FromSeconds(1), + RequestsInterval = TimeSpan.FromSeconds(3), + Timeout = TimeSpan.FromSeconds(180) + } + }, }; } } diff --git a/CapMonsterCloud.Client/Requests/TemuCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/TemuCustomTaskRequest.cs new file mode 100644 index 0000000..915a121 --- /dev/null +++ b/CapMonsterCloud.Client/Requests/TemuCustomTaskRequest.cs @@ -0,0 +1,41 @@ +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; + +namespace Zennolab.CapMonsterCloud.Requests +{ + /// + /// Temu CustomTask recognition request. + /// + /// + /// https://docs.capmonster.cloud/docs/captchas/temu-task + /// + public sealed class TemuCustomTaskRequest : CustomTaskRequestBase + { + /// + public override string Class => "Temu"; + + /// + /// Initializes Temu task with required metadata. + /// + /// + /// Cookies string from the page with captcha (document.cookie). + /// + public TemuCustomTaskRequest(string cookie) + { + Metadata = new { cookie }; + } + + /// + /// The full URL of the page where the CAPTCHA is loaded. + /// + [JsonProperty("websiteURL", Required = Required.Always)] + [Url] + public new string WebsiteUrl + { + get => base.WebsiteUrl; + set => base.WebsiteUrl = value; + } + + // userAgent, Proxy, Domains — уже есть в базе (CustomTaskRequestBase) + } +} \ No newline at end of file From 769413f39bc39cc62001febaefab542ddb9853bb Mon Sep 17 00:00:00 2001 From: SlavaObrezkov Date: Mon, 11 Aug 2025 16:42:28 +0300 Subject: [PATCH 2/6] CCL-6504 Add support for solving MTCaptcha tasks --- .../IntegrationTests.cs | 44 ++++++++++++++ .../ObjectGen.cs | 31 ++++++++++ .../Sut.cs | 3 + ...CapMonsterCloudClient_GetResultTimeouts.cs | 10 ++++ .../Requests/MTCaptchaTaskRequest.cs | 57 +++++++++++++++++++ .../Responses/MTCaptchaTaskResponse.cs | 16 ++++++ 6 files changed, 161 insertions(+) create mode 100644 CapMonsterCloud.Client/Requests/MTCaptchaTaskRequest.cs create mode 100644 CapMonsterCloud.Client/Responses/MTCaptchaTaskResponse.cs diff --git a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs index 7efb922..ef6f70c 100644 --- a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs +++ b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs @@ -1071,5 +1071,49 @@ public async Task Binance_ShouldSolve() sut.GetActualRequests().Should().BeEquivalentTo(expectedRequests); actual.Should().BeEquivalentTo(expectedResult); } + + [Test] + public async Task MTCaptcha_ShouldSolve() + { + var clientKey = Gen.RandomString(); + var taskId = Gen.RandomInt(); + + var captchaRequest = ObjectGen.MTCaptchaTask.CreateTask(); + var expectedResult = ObjectGen.MTCaptchaTask.CreateSolution(); + + var expectedRequests = new List<(RequestType Type, string ExpectedRequest)> + { + ( + Type: RequestType.CreateTask, + ExpectedRequest: JsonConvert.SerializeObject(new + { clientKey = clientKey, task = captchaRequest, softId = 53 }) + ), + ( + Type: RequestType.GetTaskResult, + ExpectedRequest: JsonConvert.SerializeObject(new { clientKey = clientKey, taskId = taskId }) + ), + }; + + var captchaResults = new List + { + new { taskId = taskId, errorId = 0, errorCode = (string)null! }, + new + { + status = "ready", + solution = new { token = expectedResult.Solution.Value }, + errorId = 0, + errorCode = (string)null! + } + }; + + var sut = new Sut(clientKey); + sut.SetupHttpServer(captchaResults); + + var actual = await sut.SolveAsync(captchaRequest); + + sut.GetActualRequests().Should().BeEquivalentTo(expectedRequests); + actual.Should().BeEquivalentTo(expectedResult); + } + } } \ No newline at end of file diff --git a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs index 652ca8c..fd6befe 100644 --- a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs +++ b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs @@ -546,5 +546,36 @@ public static CaptchaResult CreateSolution() }; } } + + public static class MTCaptchaTask + { + public static MTCaptchaTaskRequest CreateTask() + { + return new MTCaptchaTaskRequest + { + WebsiteUrl = Gen.RandomUri().ToString(), + WebsiteKey = Gen.RandomString(), + Invisible = Gen.RandomBool(), + PageAction = Gen.RandomString(), + UserAgent = Gen.UserAgent(), + Proxy = new ProxyContainer( + Gen.RandomString(), Gen.RandomInt(0, 65535), + Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) + }; + } + + public static CaptchaResult CreateSolution() + { + return new CaptchaResult + { + Error = null, + Solution = new MTCaptchaTaskResponse + { + Value = Gen.RandomString() + } + }; + } + } + } } \ No newline at end of file diff --git a/CapMonsterCloud.Client.IntegrationTests/Sut.cs b/CapMonsterCloud.Client.IntegrationTests/Sut.cs index 57cc413..3839da1 100644 --- a/CapMonsterCloud.Client.IntegrationTests/Sut.cs +++ b/CapMonsterCloud.Client.IntegrationTests/Sut.cs @@ -98,6 +98,9 @@ public async Task> SolveAsync( public async Task> SolveAsync( RecognitionComplexImageTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task> SolveAsync( + MTCaptchaTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task GetBalanceAsync() { return await _cloudClient.GetBalanceAsync(); diff --git a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs index 2cd856c..fce40f5 100644 --- a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs +++ b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs @@ -183,6 +183,16 @@ private static GetResultTimeouts GetTimeouts(Type type) Timeout = TimeSpan.FromSeconds(15) } }, + { + typeof(MTCaptchaTaskRequest), + new GetResultTimeouts + { + FirstRequestDelay = TimeSpan.FromSeconds(1), + FirstRequestNoCacheDelay = TimeSpan.FromSeconds(10), + RequestsInterval = TimeSpan.FromSeconds(3), + Timeout = TimeSpan.FromSeconds(180) + } + }, }; } } diff --git a/CapMonsterCloud.Client/Requests/MTCaptchaTaskRequest.cs b/CapMonsterCloud.Client/Requests/MTCaptchaTaskRequest.cs new file mode 100644 index 0000000..94f59a2 --- /dev/null +++ b/CapMonsterCloud.Client/Requests/MTCaptchaTaskRequest.cs @@ -0,0 +1,57 @@ +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using Zennolab.CapMonsterCloud.Responses; + +namespace Zennolab.CapMonsterCloud.Requests +{ + /// + /// MTCaptcha recognition request. + /// + /// + /// https://docs.capmonster.cloud/docs/captchas/mtcaptcha-task/ + /// + public sealed class MTCaptchaTaskRequest : CaptchaRequestBaseWithProxy + { + /// + /// Recognition task type + /// + public const string TaskType = "MTCaptchaTask"; + + /// + [JsonProperty("type", Required = Required.Always)] + public sealed override string Type => TaskType; + + /// + /// Address of a web page with MTCaptcha. + /// + [JsonProperty("websiteURL", Required = Required.Always)] + [Url] + public string WebsiteUrl { get; set; } + + /// + /// The MTCaptcha key (sk/sitekey). + /// + [JsonProperty("websiteKey", Required = Required.Always)] + [StringLength(int.MaxValue, MinimumLength = 1)] + public string WebsiteKey { get; set; } + + /// + /// true for invisible widget (has hidden confirmation field). + /// + [JsonProperty("isInvisible")] + public bool Invisible { get; set; } + + /// + /// Action value (passed as "act" and shown during token validation). + /// Provide only if it differs from default "%24". + /// + [JsonProperty("pageAction")] + public string PageAction { get; set; } + + /// + /// Browser's User-Agent (actual Windows UA recommended). + /// + [JsonProperty("userAgent")] + public string UserAgent { get; set; } + } +} \ No newline at end of file diff --git a/CapMonsterCloud.Client/Responses/MTCaptchaTaskResponse.cs b/CapMonsterCloud.Client/Responses/MTCaptchaTaskResponse.cs new file mode 100644 index 0000000..298e743 --- /dev/null +++ b/CapMonsterCloud.Client/Responses/MTCaptchaTaskResponse.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Zennolab.CapMonsterCloud.Responses +{ + /// + /// MTCaptcha recognition response + /// + public sealed class MTCaptchaTaskResponse : CaptchaResponseBase + { + /// + /// MTCaptcha token to submit to the target site. + /// + [JsonProperty("token")] + public string Value { get; set; } + } +} \ No newline at end of file From 9807a30b87782a4ceae7c48b5d6a086e4bd2fd25 Mon Sep 17 00:00:00 2001 From: SlavaObrezkov Date: Mon, 11 Aug 2025 16:37:51 +0300 Subject: [PATCH 3/6] CCL-6504 Add YidunTask integration with request/response definitions and tests --- .../IntegrationTests.cs | 43 +++++++++++ .../ObjectGen.cs | 34 +++++++++ .../Sut.cs | 3 + ...CapMonsterCloudClient_GetResultTimeouts.cs | 13 +++- .../Requests/YidunTaskRequest.cs | 74 +++++++++++++++++++ .../Responses/YidunTaskResponse.cs | 16 ++++ 6 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 CapMonsterCloud.Client/Requests/YidunTaskRequest.cs create mode 100644 CapMonsterCloud.Client/Responses/YidunTaskResponse.cs diff --git a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs index d3d88fd..4637303 100644 --- a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs +++ b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs @@ -1160,5 +1160,48 @@ public async Task MTCaptcha_ShouldSolve() sut.GetActualRequests().Should().BeEquivalentTo(expectedRequests); actual.Should().BeEquivalentTo(expectedResult); } + + [Test] + public async Task Yidun_ShouldSolve() + { + var clientKey = Gen.RandomString(); + var taskId = Gen.RandomInt(); + + var captchaRequest = ObjectGen.YidunTask.CreateTask(); + var expectedResult = ObjectGen.YidunTask.CreateSolution(); + + var expectedRequests = new List<(RequestType Type, string ExpectedRequest)> + { + ( + Type: RequestType.CreateTask, + ExpectedRequest: JsonConvert.SerializeObject(new + { clientKey = clientKey, task = captchaRequest, softId = 53 }) + ), + ( + Type: RequestType.GetTaskResult, + ExpectedRequest: JsonConvert.SerializeObject(new { clientKey = clientKey, taskId = taskId }) + ), + }; + + var captchaResults = new List + { + new { taskId = taskId, errorId = 0, errorCode = (string)null! }, + new + { + status = "ready", + solution = new { token = expectedResult.Solution.Value }, + errorId = 0, + errorCode = (string)null! + } + }; + + var sut = new Sut(clientKey); + sut.SetupHttpServer(captchaResults); + + var actual = await sut.SolveAsync(captchaRequest); + + sut.GetActualRequests().Should().BeEquivalentTo(expectedRequests); + actual.Should().BeEquivalentTo(expectedResult); + } } } \ No newline at end of file diff --git a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs index df8e73b..2b35809 100644 --- a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs +++ b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs @@ -614,5 +614,39 @@ public static CaptchaResult CreateSolution() }; } } + + public static class YidunTask + { + public static YidunTaskRequest CreateTask() + { + return new YidunTaskRequest + { + WebsiteUrl = Gen.RandomUri().ToString(), + WebsiteKey = Gen.RandomString(), + UserAgent = Gen.UserAgent(), + // Enterprise поля по желанию: + YidunGetLib = Gen.RandomUri().ToString(), + YidunApiServerSubdomain = Gen.RandomString(), + Challenge = Gen.RandomString(), + Hcg = Gen.RandomString(), + Hct = Gen.RandomLong(1, long.MaxValue), + Proxy = new ProxyContainer( + Gen.RandomString(), Gen.RandomInt(0, 65535), + Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) + }; + } + + public static CaptchaResult CreateSolution() + { + return new CaptchaResult + { + Error = null, + Solution = new YidunTaskResponse + { + Value = Gen.RandomString(), + } + }; + } + } } } \ No newline at end of file diff --git a/CapMonsterCloud.Client.IntegrationTests/Sut.cs b/CapMonsterCloud.Client.IntegrationTests/Sut.cs index 57de39b..37ffe86 100644 --- a/CapMonsterCloud.Client.IntegrationTests/Sut.cs +++ b/CapMonsterCloud.Client.IntegrationTests/Sut.cs @@ -104,6 +104,9 @@ public async Task> SolveAsync( public async Task> SolveAsync( MTCaptchaTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task> SolveAsync( + YidunTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task GetBalanceAsync() { return await _cloudClient.GetBalanceAsync(); diff --git a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs index eae38fb..8b5e47d 100644 --- a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs +++ b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs @@ -201,6 +201,17 @@ private static GetResultTimeouts GetTimeouts(Type type) RequestsInterval = TimeSpan.FromSeconds(3), Timeout = TimeSpan.FromSeconds(180) } - }, }; + }, + { + typeof(YidunTaskRequest), + new GetResultTimeouts + { + FirstRequestDelay = TimeSpan.FromSeconds(1), + FirstRequestNoCacheDelay = TimeSpan.FromSeconds(10), + RequestsInterval = TimeSpan.FromSeconds(3), + Timeout = TimeSpan.FromSeconds(180) + } + }, + }; } } diff --git a/CapMonsterCloud.Client/Requests/YidunTaskRequest.cs b/CapMonsterCloud.Client/Requests/YidunTaskRequest.cs new file mode 100644 index 0000000..7b6030c --- /dev/null +++ b/CapMonsterCloud.Client/Requests/YidunTaskRequest.cs @@ -0,0 +1,74 @@ +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using Zennolab.CapMonsterCloud.Responses; + +namespace Zennolab.CapMonsterCloud.Requests +{ + /// + /// Yidun (NECaptcha) recognition request. + /// + /// + /// https://docs.capmonster.cloud/docs/captchas/yidun-task + /// + public sealed class YidunTaskRequest : CaptchaRequestBaseWithProxy + { + /// + /// Recognition task type + /// + public const string TaskType = "YidunTask"; + + /// + [JsonProperty("type", Required = Required.Always)] + public override sealed string Type => TaskType; + + /// + /// Full URL of the page with the captcha. + /// + [JsonProperty("websiteURL", Required = Required.Always)] + [Url] + public string WebsiteUrl { get; set; } + + /// + /// The siteKey value found on the page. + /// + [JsonProperty("websiteKey", Required = Required.Always)] + [StringLength(int.MaxValue, MinimumLength = 1)] + public string WebsiteKey { get; set; } + + /// + /// Browser User-Agent (actual Windows UA recommended). + /// + [JsonProperty("userAgent")] + public string UserAgent { get; set; } + + /// + /// Full URL of JS loader (Enterprise cases). + /// + [JsonProperty("yidunGetLib")] + public string YidunGetLib { get; set; } + + /// + /// Custom API server subdomain (Enterprise cases). + /// + [JsonProperty("yidunApiServerSubdomain")] + public string YidunApiServerSubdomain { get; set; } + + /// + /// Enterprise: current captcha challenge id. + /// + [JsonProperty("challenge")] + public string Challenge { get; set; } + + /// + /// Enterprise: captcha hash. + /// + [JsonProperty("hcg")] + public string Hcg { get; set; } + + /// + /// Enterprise: numeric timestamp. + /// + [JsonProperty("hct")] + public long? Hct { get; set; } + } +} diff --git a/CapMonsterCloud.Client/Responses/YidunTaskResponse.cs b/CapMonsterCloud.Client/Responses/YidunTaskResponse.cs new file mode 100644 index 0000000..456cb67 --- /dev/null +++ b/CapMonsterCloud.Client/Responses/YidunTaskResponse.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Zennolab.CapMonsterCloud.Responses +{ + /// + /// Yidun (NECaptcha) recognition response + /// + public sealed class YidunTaskResponse : CaptchaResponseBase + { + /// + /// Yidun token to submit. + /// + [JsonProperty("token")] + public string Value { get; set; } + } +} \ No newline at end of file From 49be071acbc6d8cc21fbc12742e63cbfa24a819f Mon Sep 17 00:00:00 2001 From: SlavaObrezkov Date: Fri, 8 Aug 2025 17:54:03 +0300 Subject: [PATCH 4/6] **CCL-6504 Add support for ProsopoTask captcha type** - Implemented `ProsopoTaskRequest` and `ProsopoTaskResponse` classes. - Added integration tests for ProsopoTask solving process. - Updated `GetResultTimeouts` to include ProsopoTask-specific timeouts. - Extended `Sut` with the ability to handle ProsopoTask requests. --- .../IntegrationTests.cs | 46 +++++++++++++++++++ .../ObjectGen.cs | 27 ++++++++++- .../Sut.cs | 3 ++ ...CapMonsterCloudClient_GetResultTimeouts.cs | 10 ++++ .../Requests/ProsopoTaskRequest.cs | 38 +++++++++++++++ .../Responses/ProsopoTaskResponse.cs | 19 ++++++++ 6 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 CapMonsterCloud.Client/Requests/ProsopoTaskRequest.cs create mode 100644 CapMonsterCloud.Client/Responses/ProsopoTaskResponse.cs diff --git a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs index 4637303..cfcf589 100644 --- a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs +++ b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs @@ -1203,5 +1203,51 @@ public async Task Yidun_ShouldSolve() sut.GetActualRequests().Should().BeEquivalentTo(expectedRequests); actual.Should().BeEquivalentTo(expectedResult); } + + [Test] + public async Task Prosopo_ShouldSolve() + { + var clientKey = Gen.RandomString(); + var taskId = Gen.RandomInt(); + + var captchaRequest = ObjectGen.ProsopoTask.CreateTask(); + var expectedResult = ObjectGen.ProsopoTask.CreateSolution(); + + var expectedRequests = new List<(RequestType Type, string ExpectedRequest)> + { + ( + Type: RequestType.CreateTask, + ExpectedRequest: JsonConvert.SerializeObject(new + { clientKey = clientKey, task = captchaRequest, softId = 53 }) + ), + ( + Type: RequestType.GetTaskResult, + ExpectedRequest: JsonConvert.SerializeObject(new { clientKey = clientKey, taskId = taskId }) + ), + }; + + var captchaResults = new List + { + new { taskId = taskId, errorId = 0, errorCode = (string)null! }, + new + { + status = "ready", + solution = new + { + token = expectedResult.Solution.Value + }, + errorId = 0, + errorCode = (string)null! + } + }; + + var sut = new Sut(clientKey); + sut.SetupHttpServer(captchaResults); + + var actual = await sut.SolveAsync(captchaRequest); + + sut.GetActualRequests().Should().BeEquivalentTo(expectedRequests); + actual.Should().BeEquivalentTo(expectedResult); + } } } \ No newline at end of file diff --git a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs index 2b35809..0abcf17 100644 --- a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs +++ b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs @@ -624,7 +624,7 @@ public static YidunTaskRequest CreateTask() WebsiteUrl = Gen.RandomUri().ToString(), WebsiteKey = Gen.RandomString(), UserAgent = Gen.UserAgent(), - // Enterprise поля по желанию: + // Enterprise : YidunGetLib = Gen.RandomUri().ToString(), YidunApiServerSubdomain = Gen.RandomString(), Challenge = Gen.RandomString(), @@ -648,5 +648,30 @@ public static CaptchaResult CreateSolution() }; } } + + public static class ProsopoTask + { + public static ProsopoTaskRequest CreateTask() + { + return new ProsopoTaskRequest + { + WebsiteUrl = Gen.RandomUri().ToString(), + WebsiteKey = Gen.RandomString(), + Proxy = new ProxyContainer(Gen.RandomString(), Gen.RandomInt(0, 65535), Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) + }; + } + + public static CaptchaResult CreateSolution() + { + return new CaptchaResult + { + Error = null, + Solution = new ProsopoTaskResponse + { + Value = Gen.RandomString(), + } + }; + } + } } } \ No newline at end of file diff --git a/CapMonsterCloud.Client.IntegrationTests/Sut.cs b/CapMonsterCloud.Client.IntegrationTests/Sut.cs index 37ffe86..e068d3b 100644 --- a/CapMonsterCloud.Client.IntegrationTests/Sut.cs +++ b/CapMonsterCloud.Client.IntegrationTests/Sut.cs @@ -97,6 +97,9 @@ public async Task> SolveAsync( public async Task> SolveAsync( RecognitionComplexImageTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task> SolveAsync( + ProsopoTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task> SolveAsync( TemuCustomTaskRequest request) => await _cloudClient.SolveAsync(request); diff --git a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs index 8b5e47d..c587a40 100644 --- a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs +++ b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs @@ -212,6 +212,16 @@ private static GetResultTimeouts GetTimeouts(Type type) Timeout = TimeSpan.FromSeconds(180) } }, + { + typeof(ProsopoTaskRequest), + new GetResultTimeouts + { + FirstRequestDelay = TimeSpan.FromSeconds(1), + FirstRequestNoCacheDelay = TimeSpan.FromSeconds(10), + RequestsInterval = TimeSpan.FromSeconds(3), + Timeout = TimeSpan.FromSeconds(180) + } + }, }; } } diff --git a/CapMonsterCloud.Client/Requests/ProsopoTaskRequest.cs b/CapMonsterCloud.Client/Requests/ProsopoTaskRequest.cs new file mode 100644 index 0000000..0a2cfe7 --- /dev/null +++ b/CapMonsterCloud.Client/Requests/ProsopoTaskRequest.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using Zennolab.CapMonsterCloud.Responses; + +namespace Zennolab.CapMonsterCloud.Requests +{ + /// + /// Prosopo Procaptcha recognition request. + /// + /// + /// https://docs.capmonster.cloud/docs/captchas/prosopo-task + /// + public sealed class ProsopoTaskRequest : CaptchaRequestBaseWithProxy + { + /// + /// Recognition task type + /// + public const string TaskType = "ProsopoTask"; + + /// + [JsonProperty("type", Required = Required.Always)] + public sealed override string Type => TaskType; + + /// + /// The full URL of the CAPTCHA page. + /// + [JsonProperty("websiteURL", Required = Required.Always)] + [Url] + public string WebsiteUrl { get; set; } + + /// + /// The value of the "siteKey" parameter found on the page. + /// + [JsonProperty("websiteKey", Required = Required.Always)] + [StringLength(int.MaxValue, MinimumLength = 1)] + public string WebsiteKey { get; set; } + } +} \ No newline at end of file diff --git a/CapMonsterCloud.Client/Responses/ProsopoTaskResponse.cs b/CapMonsterCloud.Client/Responses/ProsopoTaskResponse.cs new file mode 100644 index 0000000..87069c9 --- /dev/null +++ b/CapMonsterCloud.Client/Responses/ProsopoTaskResponse.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Zennolab.CapMonsterCloud.Responses +{ + /// + /// Prosopo Procaptcha recognition response + /// + public sealed class ProsopoTaskResponse : CaptchaResponseBase + { + /// + /// Prosopo token + /// + /// + /// 0x00016c68747470733a2f2f70726f6e6f6465332e70726f736f706f2e696f... + /// + [JsonProperty("token")] + public string Value { get; set; } + } +} \ No newline at end of file From eca40b4c3a972efb44f1b7dacbf3105ed27332da Mon Sep 17 00:00:00 2001 From: SlavaObrezkov Date: Mon, 11 Aug 2025 19:54:47 +0300 Subject: [PATCH 5/6] CCL-6504 small fix (spaces) --- CapMonsterCloud.Client.IntegrationTests/Sut.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CapMonsterCloud.Client.IntegrationTests/Sut.cs b/CapMonsterCloud.Client.IntegrationTests/Sut.cs index e068d3b..8ed962a 100644 --- a/CapMonsterCloud.Client.IntegrationTests/Sut.cs +++ b/CapMonsterCloud.Client.IntegrationTests/Sut.cs @@ -97,10 +97,10 @@ public async Task> SolveAsync( public async Task> SolveAsync( RecognitionComplexImageTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task> SolveAsync( ProsopoTaskRequest request) => await _cloudClient.SolveAsync(request); - public async Task> SolveAsync( TemuCustomTaskRequest request) => await _cloudClient.SolveAsync(request); From 54fe20bbc18a5803eb2b040de03ef62efe403b9c Mon Sep 17 00:00:00 2001 From: SlavaObrezkov Date: Mon, 11 Aug 2025 20:05:04 +0300 Subject: [PATCH 6/6] CCL-6504 Up version --- CapMonsterCloud.Client/CapMonsterCloud.Client.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj b/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj index 4745628..dbf5e3d 100644 --- a/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj +++ b/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj @@ -15,8 +15,8 @@ README.md logo.png https://github.com/ZennoLab/capmonstercloud-client-dotnet - 3.0.1 - Fixed DataDome request parameters + 3.1.0 + Add Yidun, Temu, Prosopo, MTCaptcha solving support