From f8a1e193f5fae6f68186ed88e4d2f92320d6a80b Mon Sep 17 00:00:00 2001 From: Andrey Kornev Date: Tue, 23 Jun 2026 11:26:21 +0300 Subject: [PATCH] CCL-7861 update C# library, fix tests and up version --- .../IntegrationTests.cs | 92 +++++++++++++++++++ .../ObjectGen.cs | 58 +++++++++++- .../Sut.cs | 6 ++ .../CapMonsterCloud.Client.csproj | 2 +- ...CapMonsterCloudClient_GetResultTimeouts.cs | 18 ++++ .../Requests/AlibabaCustomTaskRequest.cs | 70 ++++++++++++++ .../Requests/AmazonWafRequest.cs | 9 ++ .../Requests/DataDomeCustomTaskRequest.cs | 20 ++-- .../Requests/FriendlyCustomTaskRequest.cs | 37 ++++++++ .../Requests/FunCaptchaRequest.cs | 15 +++ .../Requests/ImpervaCustomTaskRequest.cs | 11 +-- .../Requests/TenDiCustomTaskRequest.cs | 9 ++ 12 files changed, 328 insertions(+), 19 deletions(-) create mode 100644 CapMonsterCloud.Client/Requests/AlibabaCustomTaskRequest.cs create mode 100644 CapMonsterCloud.Client/Requests/FriendlyCustomTaskRequest.cs diff --git a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs index d8f8d77..7baa8e1 100644 --- a/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs +++ b/CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs @@ -1389,6 +1389,98 @@ public async Task Tspd_ShouldSolve() actual.Should().BeEquivalentTo(expectedResult); } + [Test] + public async Task Alibaba_ShouldSolve() + { + var clientKey = Gen.RandomString(); + var taskId = Gen.RandomInt(); + + var captchaRequest = ObjectGen.AlibabaTask.CreateTask(); + var expectedResult = ObjectGen.AlibabaTask.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 + { + data = expectedResult.Solution.Data + }, + 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 Friendly_ShouldSolve() + { + var clientKey = Gen.RandomString(); + var taskId = Gen.RandomInt(); + + var captchaRequest = ObjectGen.FriendlyTask.CreateTask(); + var expectedResult = ObjectGen.FriendlyTask.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 + { + data = expectedResult.Solution.Data + }, + 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 Hunt_ShouldSolve() { diff --git a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs index e643206..0403092 100644 --- a/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs +++ b/CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs @@ -107,6 +107,8 @@ public static FunCaptchaRequest CreateTask() WebsiteKey = Gen.RandomGuid(), Subdomain = Gen.RandomString(), Data = Gen.RandomString(), + UserAgent = Gen.UserAgent(), + Cookies = Gen.RandomString(), NoCache = Gen.RandomBool(), Proxy = new ProxyContainer(Gen.RandomString(), Gen.RandomInt(0, 65535), Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) }; @@ -367,6 +369,58 @@ public static CaptchaResult CreateDynamicComple } } + 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()) + { + 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 + { + Data = new Dictionary { { "token", Gen.RandomString() } } + } + }; + } + } + + public static class FriendlyTask + { + public static FriendlyCustomTaskRequest CreateTask() + { + return new FriendlyCustomTaskRequest(Gen.RandomUri().ToString()) + { + WebsiteKey = 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 + { + Data = new Dictionary { { "token", Gen.RandomString() } } + } + }; + } + } + public static class CustomTask { public static DataDomeCustomTaskRequest CreateDataDomeTask() @@ -407,7 +461,7 @@ public static CaptchaResult CreateDataDomeSolution() public static TenDiCustomTaskRequest CreateTenDiTask() { - return new TenDiCustomTaskRequest + return new TenDiCustomTaskRequest(Gen.RandomUri().ToString()) { WebsiteKey = Gen.RandomGuid(), WebsiteUrl = Gen.RandomUri().ToString(), @@ -625,6 +679,7 @@ public static AmazonWafRequest CreateTask() Context = Gen.RandomString(), Iv = Gen.RandomString(), CookieSolution = Gen.RandomBool(), + UserAgent = Gen.UserAgent(), Proxy = new ProxyContainer(Gen.RandomString(), Gen.RandomInt(0, 65535), Gen.RandomEnum(), Gen.RandomString(), Gen.RandomString()) }; } @@ -751,7 +806,6 @@ public static YidunTaskRequest CreateTask() WebsiteUrl = Gen.RandomUri().ToString(), WebsiteKey = Gen.RandomString(), UserAgent = Gen.UserAgent(), - // Enterprise ïîëÿ ïî æåëàíèþ: YidunGetLib = Gen.RandomUri().ToString(), YidunApiServerSubdomain = Gen.RandomString(), Challenge = Gen.RandomString(), diff --git a/CapMonsterCloud.Client.IntegrationTests/Sut.cs b/CapMonsterCloud.Client.IntegrationTests/Sut.cs index d7c6dba..b727f63 100644 --- a/CapMonsterCloud.Client.IntegrationTests/Sut.cs +++ b/CapMonsterCloud.Client.IntegrationTests/Sut.cs @@ -122,6 +122,12 @@ public async Task> SolveAsync( public async Task> SolveAsync( HuntCustomTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task> SolveAsync( + AlibabaCustomTaskRequest request) => await _cloudClient.SolveAsync(request); + + public async Task> SolveAsync( + FriendlyCustomTaskRequest request) => await _cloudClient.SolveAsync(request); + public async Task GetBalanceAsync() { return await _cloudClient.GetBalanceAsync(); diff --git a/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj b/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj index bd58d8d..c838764 100644 --- a/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj +++ b/CapMonsterCloud.Client/CapMonsterCloud.Client.csproj @@ -15,7 +15,7 @@ README.md logo.png https://github.com/ZennoLab/capmonstercloud-client-dotnet - 3.2.0 + 3.3.0 Add Altcha, Castle, Tspd, Hunt solving support diff --git a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs index bab1a1d..a0964b4 100644 --- a/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs +++ b/CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs @@ -258,6 +258,24 @@ private static GetResultTimeouts GetTimeouts(Type type) Timeout = TimeSpan.FromSeconds(180) } }, + { + typeof(AlibabaCustomTaskRequest), + new GetResultTimeouts + { + FirstRequestDelay = TimeSpan.FromSeconds(1), + RequestsInterval = TimeSpan.FromSeconds(3), + Timeout = TimeSpan.FromSeconds(120) + } + }, + { + typeof(FriendlyCustomTaskRequest), + new GetResultTimeouts + { + FirstRequestDelay = TimeSpan.FromSeconds(1), + RequestsInterval = TimeSpan.FromSeconds(3), + Timeout = TimeSpan.FromSeconds(180) + } + }, }; } } diff --git a/CapMonsterCloud.Client/Requests/AlibabaCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/AlibabaCustomTaskRequest.cs new file mode 100644 index 0000000..d7b5f42 --- /dev/null +++ b/CapMonsterCloud.Client/Requests/AlibabaCustomTaskRequest.cs @@ -0,0 +1,70 @@ +namespace Zennolab.CapMonsterCloud.Requests +{ + /// + /// Alibaba CustomTask recognition request. + /// + /// + /// https://docs.capmonster.cloud/docs/captchas/alibaba-task/ + /// + public sealed class AlibabaCustomTaskRequest : CustomTaskRequestBase + { + /// + public override string Class => "alibaba"; + + /// + /// + /// These values will be set to Metadata property. + /// + /// - sceneId: "sceneId": "1ww7426c4" + /// Field is required. + /// CAPTCHA scenario identifier. See documentation for how to find this value + /// + /// - prefix: "prefix": "dlw3kug" + /// Field is required. + /// CAPTCHA initialization parameter, passed in the URL of the request used to load the task text on the page. + /// For example, if the URL looks like: https://dlw3kug.captcha-open.example.aliyuncs.com/, then the value of the prefix parameter corresponds to the subdomain — dlw3kug + /// + /// + /// Other fields are optional and must only be specified if they are present on the target website: + /// + /// - userId: "userId": "..." + /// A unique identifier of the user or session on the website side. + /// + /// - userUserId: "userUserId": "..." + /// An additional (secondary) user identifier. + /// + /// - verifyType: "verifyType": "..." + /// The version or type of the captcha verification mechanism. + /// + /// - region: "region": "..." + /// The server or data center region through which the captcha is processed. + /// + /// - UserCertifyId: "UserCertifyId": "..." + /// A unique verification ID associated with the current captcha session. + /// + /// - 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. + /// + /// + public AlibabaCustomTaskRequest(string sceneId, + string prefix, + string userId = null, + string userUserId = null, + string verifyType = null, + string region = null, + string UserCertifyId = null, + string apiGetLib = null) + => + Metadata = new + { + sceneId, + prefix, + userId, + userUserId, + verifyType, + region, + UserCertifyId, + apiGetLib + }; + } +} diff --git a/CapMonsterCloud.Client/Requests/AmazonWafRequest.cs b/CapMonsterCloud.Client/Requests/AmazonWafRequest.cs index 2f8f9c6..62fb2ac 100644 --- a/CapMonsterCloud.Client/Requests/AmazonWafRequest.cs +++ b/CapMonsterCloud.Client/Requests/AmazonWafRequest.cs @@ -68,5 +68,14 @@ public class AmazonWafRequest : CaptchaRequestBaseWithProxy /// [JsonProperty("cookieSolution")] public bool CookieSolution { get; set; } + + /// + /// Browser's User-Agent which is used in emulation. + /// + /// + /// Pass only the actual User-Agent from Windows OS. + /// + [JsonProperty("userAgent")] + public string UserAgent { get; set; } } } diff --git a/CapMonsterCloud.Client/Requests/DataDomeCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/DataDomeCustomTaskRequest.cs index 4294d7f..058c520 100644 --- a/CapMonsterCloud.Client/Requests/DataDomeCustomTaskRequest.cs +++ b/CapMonsterCloud.Client/Requests/DataDomeCustomTaskRequest.cs @@ -11,20 +11,20 @@ public sealed class DataDomeCustomTaskRequest : CustomTaskRequestBase /// /// /// These values will be set to Metadata property. - /// - /// - captchaUrl: "captchaUrl": "..." - /// Field is required if metadata.htmlPageBase64 is not filled. - /// You can take the link from the page with the captcha. - /// Often it looks like https://geo.captcha-delivery.com/captcha/?initialCid=... - /// - /// - htmlPageBase64: "htmlPageBase64": "PGh0bWw+PGhlYWQ+PHRpdGxlPmJs...N0E5QTA1" - /// Field is required if 'captchaUrl' is not filled. - /// A base64 encoded html page that comes with a 403 code and a Set-Cookie: datadome="..." header in response to a get request to the target site. /// /// - datadomeCookie: "datadomeCookie": "datadome=6BvxqELMoorFNoo7GT1...JyfP_mhz" /// Field is required. Your cookies from datadome. You can get it on the page using "document.cookie" or in the Set-Cookie request header: "datadome=..." /// + /// - captchaUrl: "captchaUrl": "..." + /// Field is required. You can take the link from the page with the captcha. + /// Often it looks like https://geo.captcha-delivery.com/captcha/?initialCid=... + /// + /// - datadomeVersion: "datadomeVersion": "new" + /// DataDome solving method version. If set to "new", the updated solving method is used with support for both i.js and c.js scripts (see details below). + /// If the parameter is not specified, the legacy solving method is applied, which supports only i.js. + /// /// - public DataDomeCustomTaskRequest(string datadomeCookie, string captchaUrl = null, string htmlPageBase64 = null) => Metadata = new { datadomeCookie, captchaUrl, htmlPageBase64 }; + public DataDomeCustomTaskRequest(string datadomeCookie, string captchaUrl, string datadomeVersion = null) + => Metadata = new { datadomeCookie, captchaUrl, datadomeVersion }; } } diff --git a/CapMonsterCloud.Client/Requests/FriendlyCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/FriendlyCustomTaskRequest.cs new file mode 100644 index 0000000..f11565d --- /dev/null +++ b/CapMonsterCloud.Client/Requests/FriendlyCustomTaskRequest.cs @@ -0,0 +1,37 @@ +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; + +namespace Zennolab.CapMonsterCloud.Requests +{ + /// + /// Friendly CustomTask recognition request. + /// + /// + /// https://docs.capmonster.cloud/docs/captchas/friendly-task/ + /// + public sealed class FriendlyCustomTaskRequest : CustomTaskRequestBase + { + /// + public override string Class => "friendly"; + + /// + /// Friendly Captcha key + /// + [JsonProperty("websiteKey", Required = Required.Always)] + [StringLength(int.MaxValue, MinimumLength = 0)] + public string WebsiteKey { get; set; } + + /// + /// + /// These values will be set to Metadata property. + /// + /// - apiGetLib: "apiGetLib": "..." + /// Field is required. + /// URL of the JS file. Specify the URL depending on the captcha version: + /// V1: apiGetLib = https://cdn.jsdelivr.net/npm/friendly-challenge@X.Y.Z/widget.module.min.js, where X.Y.Z is the client version from the x-frc-client header. + /// V2: apiGetLib = URL of the site.min.js file loaded on the page.Example: https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@X.Y.Z/site.min.js, where X.Y.Z is the client version. + /// + /// + public FriendlyCustomTaskRequest(string apiGetLib) => Metadata = new { apiGetLib }; + } +} diff --git a/CapMonsterCloud.Client/Requests/FunCaptchaRequest.cs b/CapMonsterCloud.Client/Requests/FunCaptchaRequest.cs index 87d4b94..d61b551 100644 --- a/CapMonsterCloud.Client/Requests/FunCaptchaRequest.cs +++ b/CapMonsterCloud.Client/Requests/FunCaptchaRequest.cs @@ -53,6 +53,21 @@ public sealed class FunCaptchaRequest : CaptchaRequestBaseWithProxy + /// Browser's User-Agent which is used in emulation. + /// + /// + /// Pass only the actual User-Agent from Windows OS. + /// + [JsonProperty("userAgent")] + public string UserAgent { get; set; } + + /// + /// Pass additional cookies in the format: "cookieName1=value1; cookieName2=value2" + /// + [JsonProperty("cookies")] + public string Cookies { get; set; } + /// /// Set true if the site only accepts a portion of the tokens from CapMonster Cloud. /// https://zenno.link/doc-token-accept-en diff --git a/CapMonsterCloud.Client/Requests/ImpervaCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/ImpervaCustomTaskRequest.cs index 22309e4..23606fe 100644 --- a/CapMonsterCloud.Client/Requests/ImpervaCustomTaskRequest.cs +++ b/CapMonsterCloud.Client/Requests/ImpervaCustomTaskRequest.cs @@ -12,17 +12,16 @@ public class ImpervaCustomTaskRequest : CustomTaskRequestBase /// /// These values will be set to Metadata property. /// - /// - incapsulaScriptBase64: "incapsulaScriptBase64": "..." - /// the base64-encoded content of the Incapsula JavaScript script. To obtain this value, you need to go to the script page. - /// The script content loaded by this src must be encoded in base64 format. This will be the value for the parameter incapsulaScriptBase64. + /// - incapsulaScriptUrl: "incapsulaScriptUrl": "_Incapsula_Resource?SWJIYLWA=719d34d31c8e3a6e6fffd425f7e032f3" + /// Name of the Incapsula JS file /// - /// - incapsulaSessionCookie: "incapsulaSessionCookie": "l/LsGnrvyB9lNhXI8borDKa2IGcAAAAAX0qAEHheCWuNDquzwb44cw=" - /// Your cookies from Incapsula. You can obtain them on the page using "document.cookie" or in the request header Set-Cookie: "incap_sess_*=..." + /// - incapsulaCookies: "incapsulaCookies": "incap_ses_1166_2930313=br7iX33ZNCtf3HlpEXcuEDzz72cAAAAA0suDnBGrq/iA0J4oERYzjQ==; visid_incap_2930313=P3hgPVm9S8Oond1L0sXhZqfK72cAAAAAQUIPAAAAAABoMSY9xZ34RvRseJRiY6s+;" + /// Your cookies from Incapsula. You can obtain them on the page using "document.cookie" or in the request header Set-Cookie /// /// - reese84UrlEndpoint: "reese84UrlEndpoint": "Built-with-the-For-hopence-Hurleysurfecting-the-" /// 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 incapsulaScriptBase64, string incapsulaSessionCookie, string reese84UrlEndpoint) => Metadata = new { incapsulaScriptBase64, incapsulaSessionCookie, reese84UrlEndpoint }; + public ImpervaCustomTaskRequest(string incapsulaScriptUrl, string incapsulaCookies, string reese84UrlEndpoint) => Metadata = new { incapsulaScriptUrl, incapsulaCookies, reese84UrlEndpoint }; } } diff --git a/CapMonsterCloud.Client/Requests/TenDiCustomTaskRequest.cs b/CapMonsterCloud.Client/Requests/TenDiCustomTaskRequest.cs index ef9efb5..16ed749 100644 --- a/CapMonsterCloud.Client/Requests/TenDiCustomTaskRequest.cs +++ b/CapMonsterCloud.Client/Requests/TenDiCustomTaskRequest.cs @@ -18,5 +18,14 @@ public sealed class TenDiCustomTaskRequest : CustomTaskRequestBase [JsonProperty("websiteKey", Required = Required.Always)] [StringLength(int.MaxValue, MinimumLength = 1)] public string WebsiteKey { get; set; } + + /// + /// Initializes TenDI task with required metadata. + /// + /// + /// + /// 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 }; } }