Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@
}

[Test]
public async Task RecaptchaV2_IncorrectProxyPort_ShouldThrowArgumentException()

Check warning on line 827 in CapMonsterCloud.Client.IntegrationTests/IntegrationTests.cs

View workflow job for this annotation

GitHub Actions / build

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
Action actual = () => ObjectGen.RecaptchaV2.CreateTask(
proxyPort: Gen.RandomInt(65535));
Expand Down Expand Up @@ -1389,6 +1389,98 @@
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<object>
{
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<object>
{
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()
{
Expand Down
58 changes: 56 additions & 2 deletions CapMonsterCloud.Client.IntegrationTests/ObjectGen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProxyType>(), Gen.RandomString(), Gen.RandomString())
};
Expand Down Expand Up @@ -367,6 +369,58 @@ public static CaptchaResult<DynamicComplexImageTaskResponse> 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<ProxyType>(), Gen.RandomString(), Gen.RandomString())
};
}

public static CaptchaResult<CustomTaskResponse> CreateSolution()
{
return new CaptchaResult<CustomTaskResponse>
{
Error = null,
Solution = new CustomTaskResponse
{
Data = new Dictionary<string, string> { { "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<ProxyType>(), Gen.RandomString(), Gen.RandomString())
};
}

public static CaptchaResult<CustomTaskResponse> CreateSolution()
{
return new CaptchaResult<CustomTaskResponse>
{
Error = null,
Solution = new CustomTaskResponse
{
Data = new Dictionary<string, string> { { "token", Gen.RandomString() } }
}
};
}
}

public static class CustomTask
{
public static DataDomeCustomTaskRequest CreateDataDomeTask()
Expand Down Expand Up @@ -407,7 +461,7 @@ public static CaptchaResult<CustomTaskResponse> CreateDataDomeSolution()

public static TenDiCustomTaskRequest CreateTenDiTask()
{
return new TenDiCustomTaskRequest
return new TenDiCustomTaskRequest(Gen.RandomUri().ToString())
{
WebsiteKey = Gen.RandomGuid(),
WebsiteUrl = Gen.RandomUri().ToString(),
Expand Down Expand Up @@ -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<ProxyType>(), Gen.RandomString(), Gen.RandomString())
};
}
Expand Down Expand Up @@ -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(),
Expand Down
6 changes: 6 additions & 0 deletions CapMonsterCloud.Client.IntegrationTests/Sut.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ public async Task<CaptchaResult<CustomTaskResponse>> SolveAsync(
public async Task<CaptchaResult<CustomTaskResponse>> SolveAsync(
HuntCustomTaskRequest request) => await _cloudClient.SolveAsync<CustomTaskResponse>(request);

public async Task<CaptchaResult<CustomTaskResponse>> SolveAsync(
AlibabaCustomTaskRequest request) => await _cloudClient.SolveAsync<CustomTaskResponse>(request);

public async Task<CaptchaResult<CustomTaskResponse>> SolveAsync(
FriendlyCustomTaskRequest request) => await _cloudClient.SolveAsync<CustomTaskResponse>(request);

public async Task<decimal> GetBalanceAsync()
{
return await _cloudClient.GetBalanceAsync();
Expand Down
2 changes: 1 addition & 1 deletion CapMonsterCloud.Client/CapMonsterCloud.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>logo.png</PackageIcon>
<RepositoryUrl>https://github.com/ZennoLab/capmonstercloud-client-dotnet</RepositoryUrl>
<Version>3.2.0</Version>
<Version>3.3.0</Version>
<PackageReleaseNotes>Add Altcha, Castle, Tspd, Hunt solving support</PackageReleaseNotes>
</PropertyGroup>

Expand Down
18 changes: 18 additions & 0 deletions CapMonsterCloud.Client/CapMonsterCloudClient_GetResultTimeouts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
},
};
}
}
70 changes: 70 additions & 0 deletions CapMonsterCloud.Client/Requests/AlibabaCustomTaskRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
namespace Zennolab.CapMonsterCloud.Requests
{
/// <summary>
/// Alibaba CustomTask recognition request.
/// </summary>
/// <example>
/// https://docs.capmonster.cloud/docs/captchas/alibaba-task/
/// </example>
public sealed class AlibabaCustomTaskRequest : CustomTaskRequestBase
{
/// <inheritdoc/>
public override string Class => "alibaba";

/// <summary>
///
/// 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.
///
/// </summary>
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
};
}
}
9 changes: 9 additions & 0 deletions CapMonsterCloud.Client/Requests/AmazonWafRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,14 @@ public class AmazonWafRequest : CaptchaRequestBaseWithProxy<AmazonWafResponse>
/// </summary>
[JsonProperty("cookieSolution")]
public bool CookieSolution { get; set; }

/// <summary>
/// Browser's User-Agent which is used in emulation.
/// </summary>
/// <remarks>
/// Pass only the actual User-Agent from Windows OS.
/// </remarks>
[JsonProperty("userAgent")]
public string UserAgent { get; set; }
}
}
20 changes: 10 additions & 10 deletions CapMonsterCloud.Client/Requests/DataDomeCustomTaskRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ public sealed class DataDomeCustomTaskRequest : CustomTaskRequestBase
/// <summary>
///
/// 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.
///
/// </summary>
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 };
}
}
37 changes: 37 additions & 0 deletions CapMonsterCloud.Client/Requests/FriendlyCustomTaskRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations;

namespace Zennolab.CapMonsterCloud.Requests
{
/// <summary>
/// Friendly CustomTask recognition request.
/// </summary>
/// <example>
/// https://docs.capmonster.cloud/docs/captchas/friendly-task/
/// </example>
public sealed class FriendlyCustomTaskRequest : CustomTaskRequestBase
{
/// <inheritdoc/>
public override string Class => "friendly";

/// <summary>
/// Friendly Captcha key
/// </summary>
[JsonProperty("websiteKey", Required = Required.Always)]
[StringLength(int.MaxValue, MinimumLength = 0)]
public string WebsiteKey { get; set; }

/// <summary>
///
/// 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.
///
/// </summary>
public FriendlyCustomTaskRequest(string apiGetLib) => Metadata = new { apiGetLib };
}
}
Loading
Loading