From 2db6a4512d2b7b66b63674c25425a5175ec76280 Mon Sep 17 00:00:00 2001 From: Maksim Date: Wed, 9 Sep 2026 12:29:35 +0300 Subject: [PATCH] Close api contract gaps --- .../IntegrationTests.cs | 82 +++- .../ObjectGen.cs | 57 ++- .../Sut.cs | 3 + .../CapMonsterCloud.Client.Tests.csproj | 1 + .../RequestsSerializationTests.cs | 389 +++++++++++++++++- .../CapMonsterCloud.Client.csproj | 4 +- ...CapMonsterCloudClient_GetResultTimeouts.cs | 10 + .../Requests/AlibabaCustomTaskRequest.cs | 14 +- .../Requests/AmazonWafRequest.cs | 10 +- .../Requests/ComplexImageTaskBase.cs | 2 +- .../Requests/HuntCustomTaskRequest.cs | 20 + .../Requests/ImpervaCustomTaskRequest.cs | 2 +- .../Requests/ProsopoTaskRequest.cs | 6 + .../Requests/RecaptchaComplexImageTask.cs | 13 + .../Requests/RecaptchaV2EnterpriseRequest.cs | 27 ++ .../Requests/RecaptchaV2Request.cs | 6 + .../Requests/RecaptchaV3EnterpriseRequest.cs | 63 +++ .../Requests/RecaptchaV3ProxylessRequest.cs | 6 + .../RecognitionComplexImageTaskRequest.cs | 20 + .../Requests/TenDiCustomTaskRequest.cs | 2 +- .../Requests/TurnstileRequest.cs | 6 + .../Responses/ComplexImageTaskResponse.cs | 6 + .../Responses/FunCaptchaResponse.cs | 6 + .../Responses/RecaptchaV2Response.cs | 16 +- .../Responses/RecaptchaV3Response.cs | 9 +- .../Responses/TurnstileResponse.cs | 6 + README.md | 2 +- 27 files changed, 761 insertions(+), 27 deletions(-) create mode 100644 CapMonsterCloud.Client/Requests/RecaptchaV3EnterpriseRequest.cs diff --git a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs index 7baa8e1..ed5362d 100644 --- a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs +++ b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs @@ -114,7 +114,9 @@ public async Task RecaptchaV2_ShouldSolve() status = "ready", solution = new { - gRecaptchaResponse = expectedResult.Solution.Value + gRecaptchaResponse = expectedResult.Solution.Value, + userAgent = expectedResult.Solution.UserAgent, + cookies = expectedResult.Solution.Cookies }, errorId = 0, errorCode = (string)null! @@ -206,7 +208,8 @@ public async Task RecaptchaV3Proxyless_ShouldSolve() status = "ready", solution = new { - gRecaptchaResponse = expectedResult.Solution.Value + gRecaptchaResponse = expectedResult.Solution.Value, + userAgent = expectedResult.Solution.UserAgent }, errorId = 0, errorCode = (string)null! @@ -222,6 +225,69 @@ public async Task RecaptchaV3Proxyless_ShouldSolve() actual.Should().BeEquivalentTo(expectedResult); } + [Test] + public async Task RecaptchaV3Enterprise_ShouldSolve() + { + var clientKey = Gen.RandomString(); + var taskId = Gen.RandomInt(); + + var captchaRequest = ObjectGen.RecaptchaV3Enterprise.CreateTask(); + var expectedResult = ObjectGen.RecaptchaV3Enterprise.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 + { + gRecaptchaResponse = expectedResult.Solution.Value, + userAgent = expectedResult.Solution.UserAgent + }, + 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); + } + + [Test] + public async Task RecaptchaV3Enterprise_IncorrectMinScore_ShouldThrowValidationException() + { + var clientKey = Gen.RandomString(); + var captchaRequest = ObjectGen.RecaptchaV3Enterprise.CreateTask( + minScore: Gen.RandomBool() ? Gen.RandomDouble(0, 0.09) : Gen.RandomDouble(0.91, 1.5)); + + var sut = new Sut(clientKey); + sut.SetupHttpServer(new List()); + + Func actual = () => sut.SolveAsync(captchaRequest); + + _ = await actual.Should().ThrowAsync() + .WithMessage("*The field MinScore must be between 0?1 and 0?9*"); + } + [Test] public async Task FunCaptcha_ShouldSolve() { @@ -252,7 +318,8 @@ public async Task FunCaptcha_ShouldSolve() status = "ready", solution = new { - token = expectedResult.Solution.Value + token = expectedResult.Solution.Value, + userAgent = expectedResult.Solution.UserAgent }, errorId = 0, errorCode = (string)null! @@ -400,7 +467,8 @@ public async Task Turnstile_ShouldSolve() solution = new { token = expectedResult.Solution.Value, - cf_clearance = expectedResult.Solution.Clearance + cf_clearance = expectedResult.Solution.Clearance, + userAgent = expectedResult.Solution.UserAgent }, errorId = 0, errorCode = (string)null! @@ -541,7 +609,8 @@ public async Task RecaptchaComplexImageTask_ShouldSolve() status = "ready", solution = new { - answer = expectedResult.Solution.Answer + answer = expectedResult.Solution.Answer, + metadata = new { AnswerType = expectedResult.Solution.Metadata.AnswerType } }, errorId = 0, errorCode = (string)null! @@ -681,7 +750,8 @@ public async Task FunCaptchaComplexImageTask_ShouldSolve() status = "ready", solution = new { - answer = expectedResult.Solution.Answer + answer = expectedResult.Solution.Answer, + metadata = new { AnswerType = expectedResult.Solution.Metadata.AnswerType } }, errorId = 0, errorCode = (string)null! diff --git a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs index 0403092..2b3b82b 100644 --- a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs +++ b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs @@ -52,6 +52,7 @@ public static RecaptchaV2Request CreateTask( UserAgent = Gen.UserAgent(), Cookies = Gen.ListOfValues(Gen.RandomString).ToDictionary(_ => Gen.RandomString(), value => value), NoCache = Gen.RandomBool(), + IsInvisible = Gen.RandomBool(), Proxy = new ProxyContainer(Gen.RandomString(), proxyPort ?? Gen.RandomInt(0, 65535), Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) }; } @@ -64,6 +65,8 @@ public static CaptchaResult CreateSolution() Solution = new RecaptchaV2Response { Value = Gen.RandomString(), + UserAgent = Gen.UserAgent(), + Cookies = new Dictionary { { Gen.RandomString(), Gen.RandomString() } } } }; } @@ -80,7 +83,8 @@ public static RecaptchaV3ProxylessRequest CreateTask( WebsiteKey = Gen.RandomGuid(), MinScore = minScore ?? Gen.RandomDouble(0.1, 0.9), PageAction = Gen.RandomString(), - NoCache = Gen.RandomBool() + NoCache = Gen.RandomBool(), + IsEnterprise = Gen.RandomBool() }; } @@ -92,11 +96,41 @@ public static CaptchaResult CreateSolution() Solution = new RecaptchaV3Response { Value = Gen.RandomString(), + UserAgent = Gen.UserAgent() } }; } } + public static class RecaptchaV3Enterprise + { + public static RecaptchaV3EnterpriseRequest CreateTask( + double? minScore = null) + { + return new RecaptchaV3EnterpriseRequest + { + WebsiteUrl = Gen.RandomUri().ToString(), + WebsiteKey = Gen.RandomGuid(), + MinScore = minScore ?? Gen.RandomDouble(0.1, 0.9), + PageAction = Gen.RandomString(), + NoCache = Gen.RandomBool() + }; + } + + public static CaptchaResult CreateSolution() + { + return new CaptchaResult + { + Error = null, + Solution = new RecaptchaV3Response + { + Value = Gen.RandomString(), + UserAgent = Gen.UserAgent() + } + }; + } + } + public static class FunCaptcha { public static FunCaptchaRequest CreateTask() @@ -122,6 +156,7 @@ public static CaptchaResult CreateSolution() Solution = new FunCaptchaResponse { Value = Gen.RandomString(), + UserAgent = Gen.UserAgent() } }; } @@ -211,6 +246,10 @@ public static RecaptchaV2EnterpriseRequest CreateTask() WebsiteKey = Gen.RandomString(), EnterprisePayload = Gen.RandomString(), DataSValue = Gen.RandomString(), + ApiDomain = Gen.RandomString(), + PageAction = Gen.RandomString(), + Cookies = Gen.ListOfValues(Gen.RandomString).ToDictionary(_ => Gen.RandomString(), value => value), + UserAgent = Gen.UserAgent(), Proxy = new ProxyContainer(Gen.RandomString(), Gen.RandomInt(0, 65535), Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) }; } @@ -243,6 +282,7 @@ public static TurnstileRequest CreateTask() HtmlPageBase64 = Gen.RandomString(), UserAgent = Gen.UserAgent(), ApiJsUrl = Gen.RandomUri().ToString(), + Action = Gen.RandomString(), NoCache = Gen.RandomBool(), Proxy = new ProxyContainer(Gen.RandomString(), Gen.RandomInt(0, 65535), Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) }; @@ -256,7 +296,8 @@ public static CaptchaResult CreateSolution() Solution = new TurnstileResponse { Value = Gen.RandomString(), - Clearance = Gen.RandomString() + Clearance = Gen.RandomString(), + UserAgent = Gen.UserAgent() } }; } @@ -277,6 +318,8 @@ public static RecaptchaComplexImageTaskRequest CreateRecaptchaComplexImageTask() }, ImageUrls = Gen.ListOfValues(Gen.RandomUri().ToString), ImagesBase64 = Gen.ListOfValues(Gen.RandomString), + ExampleImageUrls = Gen.ListOfValues(Gen.RandomUri().ToString), + ExampleImagesBase64 = Gen.ListOfValues(Gen.RandomString), UserAgent = Gen.UserAgent() }; } @@ -322,10 +365,13 @@ public static RecognitionComplexImageTaskRequest CreateRecognitionComplexImageTa Metadata = new RecognitionComplexImageTaskRequest.RecognitionMetadata { Task = Gen.RandomString(), - TaskArgument = Gen.RandomString() + TaskArgument = Gen.RandomString(), + PayloadType = Gen.RandomString() }, ImageUrls = Gen.ListOfValues(Gen.RandomUri().ToString), ImagesBase64 = Gen.ListOfValues(Gen.RandomString), + ExampleImageUrls = Gen.ListOfValues(Gen.RandomUri().ToString), + ExampleImagesBase64 = Gen.ListOfValues(Gen.RandomString), UserAgent = Gen.UserAgent() }; } @@ -338,6 +384,7 @@ public static CaptchaResult CreateGridComplexImage Solution = new GridComplexImageTaskResponse { Answer = Gen.ListOfValues(Gen.RandomBool), + Metadata = new DynamicComplexImageTaskResponse.RecognitionMetadata { AnswerType = "Grid" } } }; } @@ -374,7 +421,8 @@ public static class AlibabaTask public static AlibabaCustomTaskRequest CreateTask() { return new AlibabaCustomTaskRequest(Gen.RandomString(), Gen.RandomString(), - Gen.RandomGuid(), Gen.RandomGuid(), Gen.RandomString(), Gen.RandomString(), Gen.RandomGuid(), Gen.RandomString()) + Gen.RandomGuid(), Gen.RandomGuid(), Gen.RandomString(), Gen.RandomString(), Gen.RandomGuid(), Gen.RandomString(), + Gen.RandomUri().ToString(), Gen.RandomBool()) { WebsiteUrl = Gen.RandomUri().ToString(), UserAgent = Gen.UserAgent(), @@ -838,6 +886,7 @@ public static ProsopoTaskRequest CreateTask() { WebsiteUrl = Gen.RandomUri().ToString(), WebsiteKey = Gen.RandomString(), + UserAgent = Gen.UserAgent(), Proxy = new ProxyContainer(Gen.RandomString(), Gen.RandomInt(0, 65535), Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) }; } diff --git a/CapMonsterCloud.Client.IntegrationTests/Sut.cs b/CapMonsterCloud.Client.IntegrationTests/Sut.cs index b727f63..3ead4ff 100644 --- a/CapMonsterCloud.Client.IntegrationTests/Sut.cs +++ b/CapMonsterCloud.Client.IntegrationTests/Sut.cs @@ -52,6 +52,9 @@ public async Task> SolveAsync ( public async Task> SolveAsync ( RecaptchaV3ProxylessRequest request) => await _cloudClient.SolveAsync(request); + + public async Task> SolveAsync ( + RecaptchaV3EnterpriseRequest request) => await _cloudClient.SolveAsync(request); public async Task> SolveAsync ( FunCaptchaRequest request) => await _cloudClient.SolveAsync(request); diff --git a/CapMonsterCloud.Client.Tests/CapMonsterCloud.Client.Tests.csproj b/CapMonsterCloud.Client.Tests/CapMonsterCloud.Client.Tests.csproj index 3c64114..65f1e8e 100644 --- a/CapMonsterCloud.Client.Tests/CapMonsterCloud.Client.Tests.csproj +++ b/CapMonsterCloud.Client.Tests/CapMonsterCloud.Client.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/CapMonsterCloud.Client.Tests/RequestsSerializationTests.cs b/CapMonsterCloud.Client.Tests/RequestsSerializationTests.cs index 9fd2190..297bfa6 100644 --- a/CapMonsterCloud.Client.Tests/RequestsSerializationTests.cs +++ b/CapMonsterCloud.Client.Tests/RequestsSerializationTests.cs @@ -25,6 +25,7 @@ public void RecaptchaV2Request__ShouldSerialize([Values] ProxyType proxyType) { "cookieA", "value#A" }, { "cookieB", "value#B" } }, + IsInvisible = true, Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") }; @@ -41,6 +42,7 @@ public void RecaptchaV2Request__ShouldSerialize([Values] ProxyType proxyType) recaptchaDataSValue = target.DataSValue, userAgent = target.UserAgent, cookies = "cookieA=value#A;cookieB=value#B", + isInvisible = target.IsInvisible, proxyAddress = target.ProxyAddress, proxyPort = target.ProxyPort, proxyType = proxyType.ToString().ToLower(), @@ -91,7 +93,8 @@ public void RecaptchaV3ProxylessRequest__ShouldSerialize() WebsiteUrl = "https://lessons.zennolab.com/captchas/recaptcha/v2_simple.php?level=high", WebsiteKey = "6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd", MinScore = 0.6, - PageAction = "some-action" + PageAction = "some-action", + IsEnterprise = true }; // Act @@ -105,7 +108,8 @@ public void RecaptchaV3ProxylessRequest__ShouldSerialize() websiteURL = target.WebsiteUrl, websiteKey = target.WebsiteKey, minScore = target.MinScore, - pageAction = target.PageAction + pageAction = target.PageAction, + isEnterprise = target.IsEnterprise })); } @@ -121,6 +125,8 @@ public void FunCaptchaRequest__ShouldSerialize([Values] ProxyType proxyType) WebsiteKey = "69A21A01-CC7B-B9C6-0F9A-E7FA06677FFC", Data = "{\"blob\":\"dyXvXANMbHj1iDyz.Qj97JtSqR2n%2BuoY1V%2FbdgbrG7p%2FmKiqdU9AwJ6MifEt0np4vfYn6TTJDJEfZDlcz9Q1XMn9przeOV%2FCr2%2FIpi%2FC1s%3D\"}", Subdomain = "mywebsite-api.funcaptcha.com", + UserAgent = "PostmanRuntime/7.29.0", + Cookies = "cookieA=value#A;cookieB=value#B", Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") }; @@ -136,6 +142,8 @@ public void FunCaptchaRequest__ShouldSerialize([Values] ProxyType proxyType) websitePublicKey = target.WebsiteKey, funcaptchaApiJSSubdomain = target.Subdomain, data = target.Data, + userAgent = target.UserAgent, + cookies = target.Cookies, proxyAddress = target.ProxyAddress, proxyPort = target.ProxyPort, proxyType = proxyType.ToString().ToLower(), @@ -182,6 +190,383 @@ public void HCaptchaRequest__ShouldSerialize([Values] ProxyType proxyType) proxyPassword = target.ProxyPassword })); } + + [Test] + public void RecaptchaV3EnterpriseRequest__ShouldSerialize() + { + // Arrange + var target = new RecaptchaV3EnterpriseRequest + { + WebsiteUrl = "https://lessons.zennolab.com/captchas/recaptcha/v3.php?level=beta", + WebsiteKey = "6Le0xVgUAAAAAIt20XEB4rVhYOODgTl00d8juDob", + MinScore = 0.6, + PageAction = "some-action", + NoCache = true + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + type = "RecaptchaV3EnterpriseTask", + websiteURL = target.WebsiteUrl, + websiteKey = target.WebsiteKey, + minScore = target.MinScore, + pageAction = target.PageAction, + nocache = target.NoCache + })); + } + + [Test] + public void RecaptchaV2EnterpriseRequest__ShouldSerialize([Values] ProxyType proxyType) + { + // Arrange + var target = new RecaptchaV2EnterpriseRequest + { + WebsiteUrl = "https://lessons.zennolab.com/captchas/recaptcha/v2_enterprise.php", + WebsiteKey = "6Lcg7CMUAAAAANphynKgn9YAgA4tQ2KI_iqRyTwd", + EnterprisePayload = "{\"s\":\"some-s-value\"}", + DataSValue = "some data-s value", + ApiDomain = "www.recaptcha.net", + PageAction = "login", + Cookies = new Dictionary + { + { "cookieA", "value#A" }, + { "cookieB", "value#B" } + }, + UserAgent = "PostmanRuntime/7.29.0", + Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + type = "RecaptchaV2EnterpriseTask", + websiteURL = target.WebsiteUrl, + websiteKey = target.WebsiteKey, + enterprisePayload = target.EnterprisePayload, + recaptchaDataSValue = target.DataSValue, + apiDomain = target.ApiDomain, + pageAction = target.PageAction, + cookies = "cookieA=value#A;cookieB=value#B", + userAgent = target.UserAgent, + proxyAddress = target.ProxyAddress, + proxyPort = target.ProxyPort, + proxyType = proxyType.ToString().ToLower(), + proxyLogin = target.ProxyLogin, + proxyPassword = target.ProxyPassword + })); + } + + [Test] + public void AmazonWafRequest__ShouldSerialize_ChallengeOnly([Values] ProxyType proxyType) + { + // Arrange: only the challenge variant, websiteKey/captchaScript/context/iv are optional in the API + var target = new AmazonWafRequest + { + WebsiteUrl = "https://example.com/", + ChallengeScript = "https://example.com/challenge.js", + CookieSolution = true, + Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + type = "AmazonTask", + websiteURL = target.WebsiteUrl, + challengeScript = target.ChallengeScript, + cookieSolution = target.CookieSolution, + userAgent = default(string), + proxyAddress = target.ProxyAddress, + proxyPort = target.ProxyPort, + proxyType = proxyType.ToString().ToLower(), + proxyLogin = target.ProxyLogin, + proxyPassword = target.ProxyPassword + })); + } + + [Test] + public void TurnstileRequest__ShouldSerialize([Values] ProxyType proxyType) + { + // Arrange + var target = new TurnstileRequest + { + WebsiteUrl = "https://lessons.zennolab.com/captchas/turnstile", + WebsiteKey = "0x4AAAAAAADnPIDROrmt1Wwj", + CloudflareTaskType = "token", + PageAction = "managed", + Data = "some cData", + PageData = "some chlPageData", + HtmlPageBase64 = "aHRtbA==", + UserAgent = "PostmanRuntime/7.29.0", + ApiJsUrl = "https://challenges.cloudflare.com/turnstile/v0/api.js", + Action = "managed", + Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + type = "TurnstileTask", + websiteURL = target.WebsiteUrl, + websiteKey = target.WebsiteKey, + cloudflareTaskType = target.CloudflareTaskType, + pageAction = target.PageAction, + data = target.Data, + pageData = target.PageData, + htmlPageBase64 = target.HtmlPageBase64, + userAgent = target.UserAgent, + apiJsUrl = target.ApiJsUrl, + action = target.Action, + proxyAddress = target.ProxyAddress, + proxyPort = target.ProxyPort, + proxyType = proxyType.ToString().ToLower(), + proxyLogin = target.ProxyLogin, + proxyPassword = target.ProxyPassword + })); + } + + [Test] + public void ProsopoTaskRequest__ShouldSerialize([Values] ProxyType proxyType) + { + // Arrange + var target = new ProsopoTaskRequest + { + WebsiteUrl = "https://example.com/", + WebsiteKey = "5EZq7z1nBnKDpeNPz3sedVMqAXWMwc8FbSGhq7yLDYEr4W6H", + UserAgent = "PostmanRuntime/7.29.0", + Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + type = "ProsopoTask", + websiteURL = target.WebsiteUrl, + websiteKey = target.WebsiteKey, + userAgent = target.UserAgent, + proxyAddress = target.ProxyAddress, + proxyPort = target.ProxyPort, + proxyType = proxyType.ToString().ToLower(), + proxyLogin = target.ProxyLogin, + proxyPassword = target.ProxyPassword + })); + } + + [Test] + public void RecognitionComplexImageTaskRequest__ShouldSerialize_WithoutWebsiteUrl() + { + // Arrange: bills_audio needs PayloadType and does not need websiteURL + var target = new RecognitionComplexImageTaskRequest + { + Metadata = new RecognitionComplexImageTaskRequest.RecognitionMetadata + { + Task = "bills_audio", + PayloadType = "Audio" + }, + ImagesBase64 = new List { "UklGRnjuAwBXQVZFZm10" } + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + @class = "recognition", + metadata = new + { + Task = target.Metadata.Task, + TaskArgument = default(string), + PayloadType = target.Metadata.PayloadType + }, + type = "ComplexImageTask", + imageUrls = default(List), + imagesBase64 = target.ImagesBase64, + userAgent = default(string) + })); + } + + [Test] + public void HuntCustomTaskRequest__ShouldSerialize_WidgetUrl([Values] ProxyType proxyType) + { + // Arrange: widgetUrl mode, data must not be sent + var target = new HuntCustomTaskRequest( + "https://example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js", + null, + "https://captcha.example.com/widget?hash=1234") + { + WebsiteUrl = "https://example.com/login", + Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + @class = "Hunt", + type = "CustomTask", + websiteURL = target.WebsiteUrl, + metadata = new + { + apiGetLib = "https://example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js", + data = default(string), + widgetUrl = "https://captcha.example.com/widget?hash=1234" + }, + userAgent = default(string), + proxyAddress = target.ProxyAddress, + proxyPort = target.ProxyPort, + proxyType = proxyType.ToString().ToLower(), + proxyLogin = target.ProxyLogin, + proxyPassword = target.ProxyPassword + })); + } + + [Test] + public void AlibabaCustomTaskRequest__ShouldSerialize([Values] ProxyType proxyType) + { + // Arrange + var target = new AlibabaCustomTaskRequest( + "1ww7426c4", + "dlw3kug", + userId: "user-id", + userUserId: "user-user-id", + verifyType: "verify-type", + region: "cn", + UserCertifyId: "certify-id", + apiGetLib: "https://example.com/captcha.js", + punishUrl: "https://example.com/_____tmd_____/punish?x5secdata=abc&x5step=2", + cookieRequired: true) + { + WebsiteUrl = "https://example.com/", + Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + @class = "alibaba", + type = "CustomTask", + websiteURL = target.WebsiteUrl, + metadata = new + { + sceneId = "1ww7426c4", + prefix = "dlw3kug", + userId = "user-id", + userUserId = "user-user-id", + verifyType = "verify-type", + region = "cn", + UserCertifyId = "certify-id", + apiGetLib = "https://example.com/captcha.js", + punishUrl = "https://example.com/_____tmd_____/punish?x5secdata=abc&x5step=2", + cookieRequired = true + }, + userAgent = default(string), + proxyAddress = target.ProxyAddress, + proxyPort = target.ProxyPort, + proxyType = proxyType.ToString().ToLower(), + proxyLogin = target.ProxyLogin, + proxyPassword = target.ProxyPassword + })); + } + + [Test] + public void ImpervaCustomTaskRequest__ShouldSerialize_WithoutReese84UrlEndpoint([Values] ProxyType proxyType) + { + // Arrange + var target = new ImpervaCustomTaskRequest("_Incapsula_Resource?SWJIYLWA=abc", "visid_incap_123=xyz; incap_ses_456=uvw") + { + WebsiteUrl = "https://example.com/", + Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + @class = "Imperva", + type = "CustomTask", + websiteURL = target.WebsiteUrl, + metadata = new + { + incapsulaScriptUrl = "_Incapsula_Resource?SWJIYLWA=abc", + incapsulaCookies = "visid_incap_123=xyz; incap_ses_456=uvw", + reese84UrlEndpoint = default(string) + }, + userAgent = default(string), + proxyAddress = target.ProxyAddress, + proxyPort = target.ProxyPort, + proxyType = proxyType.ToString().ToLower(), + proxyLogin = target.ProxyLogin, + proxyPassword = target.ProxyPassword + })); + } + + [Test] + public void TenDiCustomTaskRequest__ShouldSerialize_WithoutCaptchaUrl([Values] ProxyType proxyType) + { + // Arrange + var target = new TenDiCustomTaskRequest + { + WebsiteKey = "189123456", + WebsiteUrl = "https://example.com/", + Proxy = new ProxyContainer("proxy.com", 6045, proxyType, "login", "p@ssword") + }; + + // Act + var actual = JsonConvert.SerializeObject(target); + + // Assert + actual.Should().Be(JsonConvert.SerializeObject( + new + { + @class = "TenDI", + websiteKey = target.WebsiteKey, + type = "CustomTask", + websiteURL = target.WebsiteUrl, + metadata = new + { + captchaUrl = default(string) + }, + userAgent = default(string), + proxyAddress = target.ProxyAddress, + proxyPort = target.ProxyPort, + proxyType = proxyType.ToString().ToLower(), + proxyLogin = target.ProxyLogin, + proxyPassword = target.ProxyPassword + })); + } } } diff --git a/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj b/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj index 72e06b9..defb752 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.4.0 - Dependencies updated + 3.5.0 + Added RecaptchaV3EnterpriseRequest (reCAPTCHA v3 Enterprise). New optional parameters: reCAPTCHA v2 Enterprise apiDomain/pageAction/cookies/userAgent, reCAPTCHA v2 isInvisible, reCAPTCHA v3 isEnterprise, Turnstile action, Prosopo userAgent, ComplexImageTask exampleImageUrls/exampleImagesBase64 and recognition metadata PayloadType, Hunt widgetUrl, Alibaba punishUrl/cookieRequired. Relaxed validation: Amazon WAF websiteKey/captchaScript/challengeScript/context/iv and ComplexImageTask websiteURL are optional; Imperva reese84UrlEndpoint and TenDI captchaUrl constructor parameters are optional. Responses now expose userAgent (FunCaptcha, reCAPTCHA v2/v3, Turnstile), cookies (reCAPTCHA v2) and metadata (grid ComplexImageTask). diff --git a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs index a0964b4..0323a72 100644 --- a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs +++ b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs @@ -69,6 +69,16 @@ private static GetResultTimeouts GetTimeouts(Type type) Timeout = TimeSpan.FromSeconds(180) } }, + { + typeof(RecaptchaV3EnterpriseRequest), + new GetResultTimeouts + { + FirstRequestDelay = TimeSpan.FromSeconds(1), + FirstRequestNoCacheDelay = TimeSpan.FromSeconds(10), + RequestsInterval = TimeSpan.FromSeconds(3), + Timeout = TimeSpan.FromSeconds(180) + } + }, { typeof(ImageToTextRequest), new GetResultTimeouts diff --git a/CapMonsterCloud.Client/Requests/AlibabaCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/AlibabaCustomTaskRequest.cs index d7b5f42..6058583 100644 --- a/CapMonsterCloud.Client/Requests/AlibabaCustomTaskRequest.cs +++ b/CapMonsterCloud.Client/Requests/AlibabaCustomTaskRequest.cs @@ -45,6 +45,12 @@ public sealed class AlibabaCustomTaskRequest : CustomTaskRequestBase /// - apiGetLib: "apiGetLib": "..." /// A link to the captcha JS library used by the website. The value is generated on the client side and may change dynamically on each page render. /// + /// - punishUrl: "punishUrl": "https://example.com/_____tmd_____/punish?x5secdata=...x5step=2" + /// Full URL of the verification page for websites where Alibaba triggers verification through a separate /punish URL. + /// + /// - cookieRequired: "cookieRequired": true + /// Set to true to receive cookies together with the CAPTCHA solution (websites that require cookies after verification). + /// /// public AlibabaCustomTaskRequest(string sceneId, string prefix, @@ -53,7 +59,9 @@ public AlibabaCustomTaskRequest(string sceneId, string verifyType = null, string region = null, string UserCertifyId = null, - string apiGetLib = null) + string apiGetLib = null, + string punishUrl = null, + bool? cookieRequired = null) => Metadata = new { @@ -64,7 +72,9 @@ public AlibabaCustomTaskRequest(string sceneId, verifyType, region, UserCertifyId, - apiGetLib + apiGetLib, + punishUrl, + cookieRequired }; } } diff --git a/CapMonsterCloud.Client/Requests/AmazonWafRequest.cs b/CapMonsterCloud.Client/Requests/AmazonWafRequest.cs index 62fb2ac..c9addf4 100644 --- a/CapMonsterCloud.Client/Requests/AmazonWafRequest.cs +++ b/CapMonsterCloud.Client/Requests/AmazonWafRequest.cs @@ -31,35 +31,35 @@ public class AmazonWafRequest : CaptchaRequestBaseWithProxy /// /// A string that can be retrieved from an html page with a captcha or with javascript by executing the window.gokuProps.key /// - [JsonProperty("websiteKey", Required = Required.Always)] + [JsonProperty("websiteKey", NullValueHandling = NullValueHandling.Ignore)] [StringLength(int.MaxValue, MinimumLength = 1)] public string WebsiteKey { get; set; } /// /// Link to challenge.js (see description below the table) /// - [JsonProperty("challengeScript", Required = Required.Always)] + [JsonProperty("challengeScript", NullValueHandling = NullValueHandling.Ignore)] [StringLength(int.MaxValue, MinimumLength = 1)] public string ChallengeScript { get; set; } /// /// Link to captcha.js (see description below the table) /// - [JsonProperty("captchaScript", Required = Required.Always)] + [JsonProperty("captchaScript", NullValueHandling = NullValueHandling.Ignore)] [StringLength(int.MaxValue, MinimumLength = 1)] public string CaptchaScript { get; set; } /// /// A string that can be retrieved from an html page with a captcha or with javascript by executing the window.gokuProps.context /// - [JsonProperty("context", Required = Required.Always)] + [JsonProperty("context", NullValueHandling = NullValueHandling.Ignore)] [StringLength(int.MaxValue, MinimumLength = 1)] public string Context { get; set; } /// /// A string that can be retrieved from an html page with a captcha or with javascript by executing the window.gokuProps.iv /// - [JsonProperty("iv", Required = Required.Always)] + [JsonProperty("iv", NullValueHandling = NullValueHandling.Ignore)] [StringLength(int.MaxValue, MinimumLength = 1)] public string Iv { get; set; } diff --git a/CapMonsterCloud.Client/Requests/ComplexImageTaskBase.cs b/CapMonsterCloud.Client/Requests/ComplexImageTaskBase.cs index 56f6f18..f1712c8 100644 --- a/CapMonsterCloud.Client/Requests/ComplexImageTaskBase.cs +++ b/CapMonsterCloud.Client/Requests/ComplexImageTaskBase.cs @@ -49,7 +49,7 @@ public abstract class ComplexImageTaskRequestBase : CaptchaRequestBas /// /// Address of a webpage with captcha /// - [JsonProperty("websiteURL", Required = Required.Always)] + [JsonProperty("websiteURL", NullValueHandling = NullValueHandling.Ignore)] [Url] public string WebsiteUrl { get; set; } } diff --git a/CapMonsterCloud.Client/Requests/HuntCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/HuntCustomTaskRequest.cs index 61abbf7..dddac59 100644 --- a/CapMonsterCloud.Client/Requests/HuntCustomTaskRequest.cs +++ b/CapMonsterCloud.Client/Requests/HuntCustomTaskRequest.cs @@ -9,6 +9,7 @@ namespace Zennolab.CapMonsterCloud.Requests /// Our solving system has two operating modes: X-HD generation (fingerprint) and CAPTCHA solving. /// If you only want to generate X-HD, do not pass the data parameter. /// If you need to solve the CAPTCHA, pass the token that the target site provides during certain actions (for example, when requesting an SMS). + /// If the full widget URL is available on the page, pass it as widgetUrl instead of data. /// /// /// https://docs.capmonster.cloud/docs/captchas/hunt-task @@ -44,5 +45,24 @@ public sealed class HuntCustomTaskRequest : CustomTaskRequestBase /// The data parameter must be specified when using CAPTCHA solving mode /// public HuntCustomTaskRequest(string apiGetLib, string data) => Metadata = new { apiGetLib, data }; + + /// + /// Initializes Hunt task with metadata for CAPTCHA solving via the widget URL. + /// + /// + /// The data and widgetUrl parameters must not be combined: pass null for data when solving via widgetUrl. + /// + /// + /// "https://example.com/hd-api/external/apps/a2157wab1045d68672a63557e0n2a77edbfd15ea/api.js" + /// The full link to the api.js file. + /// + /// + /// The meta.token value received from the website. Pass null when widgetUrl is used. + /// + /// + /// "https://captcha.example.com/widget?hash=1234" + /// Full URL of the Hunt CAPTCHA widget, including the domain, path and query parameters. + /// + public HuntCustomTaskRequest(string apiGetLib, string data, string widgetUrl = null) => Metadata = new { apiGetLib, data, widgetUrl }; } } diff --git a/CapMonsterCloud.Client/Requests/ImpervaCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/ImpervaCustomTaskRequest.cs index 23606fe..925a5ac 100644 --- a/CapMonsterCloud.Client/Requests/ImpervaCustomTaskRequest.cs +++ b/CapMonsterCloud.Client/Requests/ImpervaCustomTaskRequest.cs @@ -22,6 +22,6 @@ public class ImpervaCustomTaskRequest : CustomTaskRequestBase /// The name of the endpoint where the reese84 fingerprint is sent can be found among the requests and ends with ?d=site.com /// /// - public ImpervaCustomTaskRequest(string incapsulaScriptUrl, string incapsulaCookies, string reese84UrlEndpoint) => Metadata = new { incapsulaScriptUrl, incapsulaCookies, reese84UrlEndpoint }; + public ImpervaCustomTaskRequest(string incapsulaScriptUrl, string incapsulaCookies, string reese84UrlEndpoint = null) => Metadata = new { incapsulaScriptUrl, incapsulaCookies, reese84UrlEndpoint }; } } diff --git a/CapMonsterCloud.Client/Requests/ProsopoTaskRequest.cs b/CapMonsterCloud.Client/Requests/ProsopoTaskRequest.cs index 0a2cfe7..034980b 100644 --- a/CapMonsterCloud.Client/Requests/ProsopoTaskRequest.cs +++ b/CapMonsterCloud.Client/Requests/ProsopoTaskRequest.cs @@ -34,5 +34,11 @@ public sealed class ProsopoTaskRequest : CaptchaRequestBaseWithProxy + /// Browser User-Agent. Use a current Windows OS User-Agent supported by CapMonster Cloud. + /// + [JsonProperty("userAgent", NullValueHandling = NullValueHandling.Ignore)] + public string UserAgent { get; set; } } } \ No newline at end of file diff --git a/CapMonsterCloud.Client/Requests/RecaptchaComplexImageTask.cs b/CapMonsterCloud.Client/Requests/RecaptchaComplexImageTask.cs index 3b633b3..96a3b67 100644 --- a/CapMonsterCloud.Client/Requests/RecaptchaComplexImageTask.cs +++ b/CapMonsterCloud.Client/Requests/RecaptchaComplexImageTask.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; +using System.Collections.Generic; using Zennolab.CapMonsterCloud.Responses; namespace Zennolab.CapMonsterCloud.Requests @@ -78,5 +79,17 @@ public GridSize Grid [JsonProperty("metadata")] [Required] public RecaptchaMetadata Metadata { get; set; } + + /// + /// Optional links to example images. + /// + [JsonProperty("exampleImageUrls", NullValueHandling = NullValueHandling.Ignore)] + public ICollection ExampleImageUrls { get; set; } + + /// + /// Optional base64 example images. + /// + [JsonProperty("exampleImagesBase64", NullValueHandling = NullValueHandling.Ignore)] + public ICollection ExampleImagesBase64 { get; set; } } } diff --git a/CapMonsterCloud.Client/Requests/RecaptchaV2EnterpriseRequest.cs b/CapMonsterCloud.Client/Requests/RecaptchaV2EnterpriseRequest.cs index 6b2e6eb..ef2a297 100644 --- a/CapMonsterCloud.Client/Requests/RecaptchaV2EnterpriseRequest.cs +++ b/CapMonsterCloud.Client/Requests/RecaptchaV2EnterpriseRequest.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using Zennolab.CapMonsterCloud.Responses; +using System.Collections.Generic; namespace Zennolab.CapMonsterCloud.Requests { @@ -65,5 +66,31 @@ public sealed class RecaptchaV2EnterpriseRequest : CaptchaRequestBaseWithProxy this.NoCache ?? false; + + /// + /// Domain to load reCAPTCHA Enterprise from (e.g. www.google.com or www.recaptcha.net). + /// + [JsonProperty("apiDomain", NullValueHandling = NullValueHandling.Ignore)] + public string ApiDomain { get; set; } + + /// + /// Value of the action parameter sent to Google. + /// + /// verify + [JsonProperty("pageAction", NullValueHandling = NullValueHandling.Ignore)] + public string PageAction { get; set; } + + /// + /// Additional cookies, format: "name1=value1; name2=value2". + /// + [JsonProperty("cookies", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(Json.DictionaryToSemicolonSplittedStringConverter))] + public IDictionary Cookies { get; set; } + + /// + /// Browser User-Agent to emulate. Pass only a current Windows OS UA. + /// + [JsonProperty("userAgent", NullValueHandling = NullValueHandling.Ignore)] + public string UserAgent { get; set; } } } \ No newline at end of file diff --git a/CapMonsterCloud.Client/Requests/RecaptchaV2Request.cs b/CapMonsterCloud.Client/Requests/RecaptchaV2Request.cs index 9db01d7..1f7ff54 100644 --- a/CapMonsterCloud.Client/Requests/RecaptchaV2Request.cs +++ b/CapMonsterCloud.Client/Requests/RecaptchaV2Request.cs @@ -69,5 +69,11 @@ public sealed class RecaptchaV2Request : CaptchaRequestBaseWithProxy this.NoCache ?? false; + + /// + /// true if the captcha is invisible, i.e. has a hidden field for confirmation, no checkbox. If a bot is suspected, an additional check is called. + /// + [JsonProperty("isInvisible", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsInvisible { get; set; } } } \ No newline at end of file diff --git a/CapMonsterCloud.Client/Requests/RecaptchaV3EnterpriseRequest.cs b/CapMonsterCloud.Client/Requests/RecaptchaV3EnterpriseRequest.cs new file mode 100644 index 0000000..0102c37 --- /dev/null +++ b/CapMonsterCloud.Client/Requests/RecaptchaV3EnterpriseRequest.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; +using Zennolab.CapMonsterCloud.Responses; + +namespace Zennolab.CapMonsterCloud.Requests +{ + /// + /// reCAPTCHA v3 Enterprise recognition request. + /// The task is executed through CapMonster Cloud proxy servers (proxyless). + /// + /// + /// https://docs.capmonster.cloud/docs/captchas/recaptcha-v3-enterprise-task + /// + public sealed class RecaptchaV3EnterpriseRequest : CaptchaRequestBase + { + /// + /// Recognition task type + /// + public const string TaskType = "RecaptchaV3EnterpriseTask"; + + /// + [JsonProperty("type", Required = Required.Always)] + public override string Type => TaskType; + + /// + /// Address of a webpage with captcha + /// + [JsonProperty("websiteURL", Required = Required.Always)] + [Url] + public string WebsiteUrl { get; set; } + + /// + /// Recaptcha website key + /// + /// + /// 6Le0xVgUAAAAAIt20XEB4rVhYOODgTl00d8juDob + /// + [JsonProperty("websiteKey", Required = Required.Always)] + [StringLength(int.MaxValue, MinimumLength = 1)] + public string WebsiteKey { get; set; } + + /// + /// Minimum acceptable score, from 0.1 to 0.9. Optional. + /// + [JsonProperty("minScore", NullValueHandling = NullValueHandling.Ignore)] + [Range(0.1, 0.9)] + public double? MinScore { get; set; } + + /// + /// Value of the action parameter passed to Google. The server defaults to "verify". Optional. + /// + [JsonProperty("pageAction", NullValueHandling = NullValueHandling.Ignore)] + public string PageAction { get; set; } + + /// + /// Set true to disable the reuse of previously obtained tokens (see "Token acceptance issues" in the docs). Optional. + /// + [JsonProperty("nocache", NullValueHandling = NullValueHandling.Ignore)] + public bool? NoCache { get; set; } + + internal override bool UseNoCache => this.NoCache ?? false; + } +} diff --git a/CapMonsterCloud.Client/Requests/RecaptchaV3ProxylessRequest.cs b/CapMonsterCloud.Client/Requests/RecaptchaV3ProxylessRequest.cs index b38f42f..5cfd824 100644 --- a/CapMonsterCloud.Client/Requests/RecaptchaV3ProxylessRequest.cs +++ b/CapMonsterCloud.Client/Requests/RecaptchaV3ProxylessRequest.cs @@ -65,5 +65,11 @@ public sealed class RecaptchaV3ProxylessRequest : CaptchaRequestBase this.NoCache ?? false; + + /// + /// Set true to solve v3 Enterprise via this task type. + /// + [JsonProperty("isEnterprise", NullValueHandling = NullValueHandling.Ignore)] + public bool? IsEnterprise { get; set; } } } diff --git a/CapMonsterCloud.Client/Requests/RecognitionComplexImageTaskRequest.cs b/CapMonsterCloud.Client/Requests/RecognitionComplexImageTaskRequest.cs index 679f946..7f54a54 100644 --- a/CapMonsterCloud.Client/Requests/RecognitionComplexImageTaskRequest.cs +++ b/CapMonsterCloud.Client/Requests/RecognitionComplexImageTaskRequest.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using Zennolab.CapMonsterCloud.Responses; +using System.Collections.Generic; namespace Zennolab.CapMonsterCloud.Requests { @@ -35,6 +36,13 @@ public sealed class RecognitionMetadata /// [JsonProperty("TaskArgument")] public string TaskArgument { get; set; } + + /// + /// recognition: payload kind (e.g. "Audio"). + /// + /// Required for: bills_audio (see docs) + [JsonProperty("PayloadType", NullValueHandling = NullValueHandling.Ignore)] + public string PayloadType { get; set; } } /// @@ -43,5 +51,17 @@ public sealed class RecognitionMetadata [JsonProperty("metadata")] [Required] public RecognitionMetadata Metadata { get; set; } + + /// + /// Optional links to example images. + /// + [JsonProperty("exampleImageUrls", NullValueHandling = NullValueHandling.Ignore)] + public ICollection ExampleImageUrls { get; set; } + + /// + /// Optional base64 example images. + /// + [JsonProperty("exampleImagesBase64", NullValueHandling = NullValueHandling.Ignore)] + public ICollection ExampleImagesBase64 { get; set; } } } diff --git a/CapMonsterCloud.Client/Requests/TenDiCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/TenDiCustomTaskRequest.cs index 16ed749..4890555 100644 --- a/CapMonsterCloud.Client/Requests/TenDiCustomTaskRequest.cs +++ b/CapMonsterCloud.Client/Requests/TenDiCustomTaskRequest.cs @@ -26,6 +26,6 @@ public sealed class TenDiCustomTaskRequest : CustomTaskRequestBase /// /// Link to the captcha script. It usually ends with TCaptcha.js or TCaptcha-global.js. You can find it in the list of requests /// - public TenDiCustomTaskRequest(string captchaUrl) => Metadata = new { captchaUrl }; + public TenDiCustomTaskRequest(string captchaUrl = null) => Metadata = new { captchaUrl }; } } diff --git a/CapMonsterCloud.Client/Requests/TurnstileRequest.cs b/CapMonsterCloud.Client/Requests/TurnstileRequest.cs index f24e524..ee4f1d5 100644 --- a/CapMonsterCloud.Client/Requests/TurnstileRequest.cs +++ b/CapMonsterCloud.Client/Requests/TurnstileRequest.cs @@ -97,5 +97,11 @@ public sealed class TurnstileRequest : CaptchaRequestBaseWithProxy [JsonProperty("apiJsUrl")] public string ApiJsUrl { get; set; } + + /// + /// Alternative to pageAction accepted by the backend. + /// + [JsonProperty("action", NullValueHandling = NullValueHandling.Ignore)] + public string Action { get; set; } } } \ No newline at end of file diff --git a/CapMonsterCloud.Client/Responses/ComplexImageTaskResponse.cs b/CapMonsterCloud.Client/Responses/ComplexImageTaskResponse.cs index 3c4cd32..34a19ef 100644 --- a/CapMonsterCloud.Client/Responses/ComplexImageTaskResponse.cs +++ b/CapMonsterCloud.Client/Responses/ComplexImageTaskResponse.cs @@ -16,5 +16,11 @@ public class GridComplexImageTaskResponse : CaptchaResponseBase /// [JsonProperty("answer")] public ICollection Answer { get; set; } + + /// + /// Metadata with the answer type, when returned + /// + [JsonProperty("metadata")] + public DynamicComplexImageTaskResponse.RecognitionMetadata Metadata { get; set; } } } diff --git a/CapMonsterCloud.Client/Responses/FunCaptchaResponse.cs b/CapMonsterCloud.Client/Responses/FunCaptchaResponse.cs index b32c090..530c8f6 100644 --- a/CapMonsterCloud.Client/Responses/FunCaptchaResponse.cs +++ b/CapMonsterCloud.Client/Responses/FunCaptchaResponse.cs @@ -17,5 +17,11 @@ public class FunCaptchaResponse : CaptchaResponseBase /// [JsonProperty("token")] public string Value { get; set; } + + /// + /// User-Agent used while solving. + /// + [JsonProperty("userAgent")] + public string UserAgent { get; set; } } } diff --git a/CapMonsterCloud.Client/Responses/RecaptchaV2Response.cs b/CapMonsterCloud.Client/Responses/RecaptchaV2Response.cs index b4295fd..68d71e1 100644 --- a/CapMonsterCloud.Client/Responses/RecaptchaV2Response.cs +++ b/CapMonsterCloud.Client/Responses/RecaptchaV2Response.cs @@ -1,9 +1,23 @@ -namespace Zennolab.CapMonsterCloud.Responses +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Zennolab.CapMonsterCloud.Responses { /// /// RecaptchaV2 recognition response /// public sealed class RecaptchaV2Response : RecaptchaResponseBase { + /// + /// User-Agent to resubmit with the token, when returned. + /// + [JsonProperty("userAgent")] + public string UserAgent { get; set; } + + /// + /// Additional cookies, when returned. + /// + [JsonProperty("cookies", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary Cookies { get; set; } } } diff --git a/CapMonsterCloud.Client/Responses/RecaptchaV3Response.cs b/CapMonsterCloud.Client/Responses/RecaptchaV3Response.cs index 4ac05f8..3ed7132 100644 --- a/CapMonsterCloud.Client/Responses/RecaptchaV3Response.cs +++ b/CapMonsterCloud.Client/Responses/RecaptchaV3Response.cs @@ -1,9 +1,16 @@ -namespace Zennolab.CapMonsterCloud.Responses +using Newtonsoft.Json; + +namespace Zennolab.CapMonsterCloud.Responses { /// /// RecaptchaV3 recognition response /// public sealed class RecaptchaV3Response : RecaptchaResponseBase { + /// + /// User-Agent to resubmit with the token, when returned. + /// + [JsonProperty("userAgent")] + public string UserAgent { get; set; } } } diff --git a/CapMonsterCloud.Client/Responses/TurnstileResponse.cs b/CapMonsterCloud.Client/Responses/TurnstileResponse.cs index ceae626..9bbc7ab 100644 --- a/CapMonsterCloud.Client/Responses/TurnstileResponse.cs +++ b/CapMonsterCloud.Client/Responses/TurnstileResponse.cs @@ -21,5 +21,11 @@ public sealed class TurnstileResponse : CaptchaResponseBase /// [JsonProperty("cf_clearance")] public string Clearance { get; set; } + + /// + /// User-Agent used while solving. + /// + [JsonProperty("userAgent")] + public string UserAgent { get; set; } } } diff --git a/README.md b/README.md index 81f36c6..e1f4e89 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ See the official [Supported CAPTCHA Types](https://docs.capmonster.cloud/docs/ca | Task family | Example request classes in this SDK | | :--- | :--- | -| **reCAPTCHA** | `RecaptchaV2Request`, `RecaptchaV2EnterpriseRequest`, `RecaptchaV3ProxylessRequest` | +| **reCAPTCHA** | `RecaptchaV2Request`, `RecaptchaV2EnterpriseRequest`, `RecaptchaV3ProxylessRequest`, `RecaptchaV3EnterpriseRequest` | | **Cloudflare Turnstile** | `TurnstileRequest` | | **GeeTest** | `GeeTestRequest` | | **Amazon WAF** | `AmazonWafRequest` |