commit 3d27f14905cfaa6fd95ac35a228a60547d4280ea Author: sam Date: Sat Oct 12 01:32:31 2024 +0100 first commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..12b38b8 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c51efbc --- /dev/null +++ b/README.md @@ -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) +``` + diff --git a/ezcaptcha.go b/ezcaptcha.go new file mode 100644 index 0000000..176b3a2 --- /dev/null +++ b/ezcaptcha.go @@ -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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..391b4ff --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/samjblack/ezcaptcha-client-go + +go 1.22.5 diff --git a/requests.go b/requests.go new file mode 100644 index 0000000..c28e7b1 --- /dev/null +++ b/requests.go @@ -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 +} diff --git a/tasks.go b/tasks.go new file mode 100644 index 0000000..5110959 --- /dev/null +++ b/tasks.go @@ -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) +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..967dd39 --- /dev/null +++ b/types.go @@ -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"` +}