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