first commit
This commit is contained in:
commit
3d27f14905
24
LICENSE
Normal file
24
LICENSE
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
BSD 2-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2024, samjblack
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
56
README.md
Normal file
56
README.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# EzCaptcha Client Go
|
||||||
|
|
||||||
|
## Supported Captchas
|
||||||
|
- AkamaiWeb
|
||||||
|
- AkamaiBMP
|
||||||
|
- HCaptcha + Enterprise
|
||||||
|
- ReCaptchaV2 + Enterprise
|
||||||
|
- ReCaptchaV3 + Enterprise
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
```sh
|
||||||
|
go get -u github.com/samjblack/ezcaptcha-client-go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```go
|
||||||
|
client, err := ezcaptcha.NewClient("your-api-key", "", 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
task, err := client.NewReCaptchaV2Task(&ezcaptcha.ReCaptchaV2{
|
||||||
|
// For availabe types please refer to ezcaptcha's docs (https://ezcaptcha.atlassian.net/wiki/spaces/IS/pages/7045121/EzCaptcha+API+Docs+English)
|
||||||
|
Type: "ReCaptchaV2TaskProxyless",
|
||||||
|
WebsiteURL: "https://www.google.com/recaptcha/api2/demo",
|
||||||
|
WebsiteKey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
|
||||||
|
IsInvisible: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
result, err := client.GetResult(task)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(result.Solution.GRecaptchaResponse)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AkamaiBMP Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
client, err := ezcaptcha.NewClient("your-api-key", "", 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
result, err := client.SolveAkamaiBMP(&ezcaptcha.AkamaiBMP{
|
||||||
|
Type: "AkamaiBMPTaskProxyless",
|
||||||
|
BundleID: "example.bundle.id",
|
||||||
|
Device: "ios",
|
||||||
|
Version: "3.3.5",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(result.Solution.Sensor)
|
||||||
|
```
|
||||||
|
|
71
ezcaptcha.go
Normal file
71
ezcaptcha.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package ezcaptcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ApiURL string = "https://api.ez-captcha.com"
|
||||||
|
SyncApiURL string = "https://sync.ez-captcha.com"
|
||||||
|
CreateTaskEndPoint string = "/createTask"
|
||||||
|
CreateSyncTaskEndpoint string = "/createSyncTask"
|
||||||
|
GetTaskResultEndPoint string = "/getTaskResult"
|
||||||
|
GetBalanceEndPoint string = "/getBalance"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewClient(clientKey, appID string, attempts int) (*Client, error) {
|
||||||
|
if attempts == 0 {
|
||||||
|
attempts = 45
|
||||||
|
} else if attempts < 0 {
|
||||||
|
return nil, fmt.Errorf("attempts cannot have a negative value")
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
ClientKey: clientKey,
|
||||||
|
AppID: appID,
|
||||||
|
Attempts: attempts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetResult(task *CreateTaskResponse) (*SolutionResponse, error) {
|
||||||
|
response := &SolutionResponse{}
|
||||||
|
var err error
|
||||||
|
for i := 0; i < c.Attempts; i++ {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
response, err = c.getResult(task)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if response.ErrorID != 0 {
|
||||||
|
return nil, errors.New(fmt.Sprintf("%s", response.ErrorDescription))
|
||||||
|
}
|
||||||
|
if response.Status == "ready" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetBalance() (int, error) {
|
||||||
|
requestData := GetBalanceRequest{
|
||||||
|
ClientKey: c.ClientKey,
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(requestData)
|
||||||
|
resp, err := http.Post(fmt.Sprintf("%s%s", ApiURL, GetBalanceEndPoint), "application/json", bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
response := &GetBalanceResponse{}
|
||||||
|
b, _ = io.ReadAll(resp.Body)
|
||||||
|
json.Unmarshal(b, response)
|
||||||
|
if response.ErrorID != 0 {
|
||||||
|
return 0, errors.New(fmt.Sprintf("%s", response.ErrorDescription))
|
||||||
|
}
|
||||||
|
return response.Balance, nil
|
||||||
|
}
|
3
go.mod
Normal file
3
go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/samjblack/ezcaptcha-client-go
|
||||||
|
|
||||||
|
go 1.22.5
|
73
requests.go
Normal file
73
requests.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package ezcaptcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) getResult(task *CreateTaskResponse) (*SolutionResponse, error) {
|
||||||
|
requestData := SolutionRequest{
|
||||||
|
ClientKey: c.ClientKey,
|
||||||
|
TaskID: task.TaskID,
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(requestData)
|
||||||
|
resp, err := http.Post(fmt.Sprintf("%s%s", ApiURL, GetTaskResultEndPoint), "application/json", bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response := &SolutionResponse{}
|
||||||
|
b, _ = io.ReadAll(resp.Body)
|
||||||
|
json.Unmarshal(b, response)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) createTask(task interface{}) (*CreateTaskResponse, error) {
|
||||||
|
requestData := &TaskRequest{
|
||||||
|
Client: *c,
|
||||||
|
Task: task,
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(requestData)
|
||||||
|
resp, err := http.Post(fmt.Sprintf("%s%s", ApiURL, CreateTaskEndPoint), "application/json", bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
b, _ = io.ReadAll(resp.Body)
|
||||||
|
response := &CreateTaskResponse{}
|
||||||
|
err = json.Unmarshal(b, response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if response.ErrorID != 0 {
|
||||||
|
return nil, fmt.Errorf(response.ErrorDescription)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) solveAkamai(task interface{}) (*AkamaiResponse, error) {
|
||||||
|
requestData := &TaskRequest{
|
||||||
|
Client: *c,
|
||||||
|
Task: task,
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(requestData)
|
||||||
|
resp, err := http.Post(fmt.Sprintf("%s%s", SyncApiURL, CreateSyncTaskEndpoint), "application/json", bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
b, _ = io.ReadAll(resp.Body)
|
||||||
|
response := &AkamaiResponse{}
|
||||||
|
err = json.Unmarshal(b, response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if response.ErrorID != 0 {
|
||||||
|
return nil, errors.New(fmt.Sprintf("%s", response.ErrorDescription))
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
21
tasks.go
Normal file
21
tasks.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package ezcaptcha
|
||||||
|
|
||||||
|
func (c *Client) NewReCaptchaV2Task(task *ReCaptchaV2) (*CreateTaskResponse, error) {
|
||||||
|
return c.createTask(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) NewReCaptchaV3Task(task *ReCaptchaV3) (*CreateTaskResponse, error) {
|
||||||
|
return c.createTask(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) NewHCaptchaTask(task *HCaptcha) (*CreateTaskResponse, error) {
|
||||||
|
return c.createTask(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SolveAkamai(task *Akamai) (*AkamaiResponse, error) {
|
||||||
|
return c.solveAkamai(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SolveAkamaiBMP(task *AkamaiBMP) (*AkamaiResponse, error) {
|
||||||
|
return c.solveAkamai(task)
|
||||||
|
}
|
99
types.go
Normal file
99
types.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package ezcaptcha
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
ClientKey string `json:"clientKey"`
|
||||||
|
AppID string `json:"appId,omitempty"`
|
||||||
|
Attempts int
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetBalanceRequest struct {
|
||||||
|
ClientKey string `json:"clientKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structs which are using for creating tasks
|
||||||
|
|
||||||
|
type TaskRequest struct {
|
||||||
|
Client
|
||||||
|
Task any `json:"task"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SolutionRequest struct {
|
||||||
|
ClientKey string `json:"clientKey"`
|
||||||
|
TaskID string `json:"taskId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReCaptchaV2 struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
WebsiteURL string `json:"websiteURL"`
|
||||||
|
WebsiteKey string `json:"websiteKey"`
|
||||||
|
IsInvisible bool `json:"isInvisible,omitempty"`
|
||||||
|
Sa string `json:"sa,omitempty"`
|
||||||
|
S string `json:"s,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReCaptchaV3 struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
WebsiteURL string `json:"websiteURL"`
|
||||||
|
WebsiteKey string `json:"websiteKey"`
|
||||||
|
IsInvisible bool `json:"isInvisible,omitempty"`
|
||||||
|
PageAction string `json:"pageAction,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HCaptcha struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
WebsiteURL string `json:"websiteURL"`
|
||||||
|
WebsiteKey string `json:"websiteKey"`
|
||||||
|
EnterpricePayload string `json:"enterprisePayload,omitempty"`
|
||||||
|
IsInvisible bool `json:"isInvisible,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Akamai struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
PageURL string `json:"pageUrl"`
|
||||||
|
Bmsz string `json:"bmsz"`
|
||||||
|
UA string `json:"ua"`
|
||||||
|
Lang string `json:"lang"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AkamaiBMP struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
BundleID string `json:"bundleID"`
|
||||||
|
Device string `json:"device"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structs to parse the response from their api
|
||||||
|
|
||||||
|
type CreateTaskResponse struct {
|
||||||
|
ErrorID int `json:"errorId"`
|
||||||
|
ErrorCode string `json:"errorCode,omitempty"`
|
||||||
|
ErrorDescription string `json:"errorDescription,omitempty"`
|
||||||
|
TaskID string `json:"taskId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SolutionResponse struct {
|
||||||
|
ErrorID int `json:"errorId"`
|
||||||
|
ErrorCode any `json:"errorCode,omitempty"`
|
||||||
|
ErrorDescription any `json:"errorDescription,omitempty"`
|
||||||
|
Solution struct {
|
||||||
|
GRecaptchaResponse string `json:"gRecaptchaResponse,omitempty"`
|
||||||
|
} `json:"solution"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AkamaiResponse struct {
|
||||||
|
ErrorID int `json:"errorId"`
|
||||||
|
ErrorCode any `json:"errorCode,omitempty"`
|
||||||
|
ErrorDescription any `json:"errorDescription,omitempty"`
|
||||||
|
Solution struct {
|
||||||
|
Payload string `json:"payload,omitempty"`
|
||||||
|
Sensor string `json:"sensor,omitempty"`
|
||||||
|
} `json:"solution"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
type GetBalanceResponse struct {
|
||||||
|
Balance int `json:"balance,omitempty"`
|
||||||
|
ErrorID int `json:"errorId"`
|
||||||
|
ErrorCode any `json:"errorCode,omitempty"`
|
||||||
|
ErrorDescription any `json:"errorDescription,omitempty"`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user