mirror of
https://github.com/aykhans/sarin.git
synced 2026-04-14 20:19:37 +00:00
Compare commits
2 Commits
006029aad1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e0ef72778 | |||
|
|
8d10198f02 |
8
go.mod
8
go.mod
@@ -15,7 +15,7 @@ require (
|
||||
github.com/yuin/gopher-lua v1.1.2
|
||||
go.aykhans.me/utils v1.0.7
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4
|
||||
golang.org/x/net v0.52.0
|
||||
golang.org/x/net v0.53.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -52,7 +52,7 @@ require (
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yuin/goldmark v1.8.2 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.6 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.41.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/term v0.42.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
)
|
||||
|
||||
16
go.sum
16
go.sum
@@ -109,16 +109,16 @@ go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
package sarin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.aykhans.me/sarin/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
captchaPollInterval = 5 * time.Second
|
||||
captchaTimeout = 120 * time.Second
|
||||
)
|
||||
|
||||
var captchaHTTPClient = &http.Client{Timeout: captchaTimeout}
|
||||
|
||||
// solveCaptcha creates a task and polls for the result.
|
||||
// baseURL is the service API base (e.g. "https://api.2captcha.com").
|
||||
// taskIDIsString controls whether taskId is sent back as a string or number.
|
||||
// solutionKey is the field name in the solution object that holds the token.
|
||||
func solveCaptcha(baseURL, apiKey string, task map[string]any, solutionKey string, taskIDIsString bool) (string, error) {
|
||||
if apiKey == "" {
|
||||
return "", types.ErrCaptchaKeyEmpty
|
||||
}
|
||||
|
||||
taskID, err := captchaCreateTask(baseURL, apiKey, task)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return captchaPollResult(baseURL, apiKey, taskID, solutionKey, taskIDIsString)
|
||||
}
|
||||
|
||||
func captchaCreateTask(baseURL, apiKey string, task map[string]any) (string, error) {
|
||||
body := map[string]any{
|
||||
"clientKey": apiKey,
|
||||
"task": task,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", types.NewCaptchaRequestError("createTask", err)
|
||||
}
|
||||
|
||||
resp, err := captchaHTTPClient.Post(
|
||||
baseURL+"/createTask",
|
||||
"application/json",
|
||||
bytes.NewReader(data),
|
||||
)
|
||||
if err != nil {
|
||||
return "", types.NewCaptchaRequestError("createTask", err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
var result struct {
|
||||
ErrorID int `json:"errorId"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorDescription string `json:"errorDescription"`
|
||||
TaskID json.RawMessage `json:"taskId"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", types.NewCaptchaRequestError("createTask", err)
|
||||
}
|
||||
|
||||
if result.ErrorID != 0 {
|
||||
return "", types.NewCaptchaAPIError("createTask", result.ErrorCode, result.ErrorDescription)
|
||||
}
|
||||
|
||||
// taskId may be a JSON number (2captcha, anti-captcha) or a quoted string (capsolver UUIDs).
|
||||
// Strip surrounding quotes if present so we always work with the underlying value.
|
||||
return strings.Trim(string(result.TaskID), `"`), nil
|
||||
}
|
||||
|
||||
func captchaPollResult(baseURL, apiKey, taskID, solutionKey string, taskIDIsString bool) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), captchaTimeout)
|
||||
defer cancel()
|
||||
|
||||
ticker := time.NewTicker(captchaPollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return "", types.NewCaptchaTimeoutError(taskID)
|
||||
case <-ticker.C:
|
||||
token, done, err := captchaGetTaskResult(baseURL, apiKey, taskID, solutionKey, taskIDIsString)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if done {
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func captchaGetTaskResult(baseURL, apiKey, taskID, solutionKey string, taskIDIsString bool) (string, bool, error) {
|
||||
var bodyMap map[string]any
|
||||
if taskIDIsString {
|
||||
bodyMap = map[string]any{"clientKey": apiKey, "taskId": taskID}
|
||||
} else {
|
||||
bodyMap = map[string]any{"clientKey": apiKey, "taskId": json.Number(taskID)}
|
||||
}
|
||||
|
||||
data, err := json.Marshal(bodyMap)
|
||||
if err != nil {
|
||||
return "", false, types.NewCaptchaRequestError("getTaskResult", err)
|
||||
}
|
||||
|
||||
resp, err := captchaHTTPClient.Post(
|
||||
baseURL+"/getTaskResult",
|
||||
"application/json",
|
||||
bytes.NewReader(data),
|
||||
)
|
||||
if err != nil {
|
||||
return "", false, types.NewCaptchaRequestError("getTaskResult", err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
var result struct {
|
||||
ErrorID int `json:"errorId"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorDescription string `json:"errorDescription"`
|
||||
Status string `json:"status"`
|
||||
Solution map[string]any `json:"solution"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", false, types.NewCaptchaRequestError("getTaskResult", err)
|
||||
}
|
||||
|
||||
if result.ErrorID != 0 {
|
||||
return "", false, types.NewCaptchaAPIError("getTaskResult", result.ErrorCode, result.ErrorDescription)
|
||||
}
|
||||
|
||||
if result.Status == "processing" || result.Status == "idle" {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
token, ok := result.Solution[solutionKey]
|
||||
if !ok {
|
||||
return "", false, types.NewCaptchaSolutionKeyError(solutionKey)
|
||||
}
|
||||
tokenStr, ok := token.(string)
|
||||
if !ok {
|
||||
return "", false, types.NewCaptchaSolutionKeyError(solutionKey)
|
||||
}
|
||||
|
||||
return tokenStr, true, nil
|
||||
}
|
||||
|
||||
// ======================================== 2Captcha ========================================
|
||||
|
||||
const twoCaptchaBaseURL = "https://api.2captcha.com"
|
||||
|
||||
func twoCaptchaSolveRecaptchaV2(apiKey, websiteURL, websiteKey string) (string, error) {
|
||||
return solveCaptcha(twoCaptchaBaseURL, apiKey, map[string]any{
|
||||
"type": "RecaptchaV2TaskProxyless",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
}, "gRecaptchaResponse", false)
|
||||
}
|
||||
|
||||
func twoCaptchaSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction string) (string, error) {
|
||||
task := map[string]any{
|
||||
"type": "RecaptchaV3TaskProxyless",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
}
|
||||
if pageAction != "" {
|
||||
task["pageAction"] = pageAction
|
||||
}
|
||||
return solveCaptcha(twoCaptchaBaseURL, apiKey, task, "gRecaptchaResponse", false)
|
||||
}
|
||||
|
||||
func twoCaptchaSolveTurnstile(apiKey, websiteURL, websiteKey, cData string) (string, error) {
|
||||
task := map[string]any{
|
||||
"type": "TurnstileTaskProxyless",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
}
|
||||
if cData != "" {
|
||||
task["data"] = cData
|
||||
}
|
||||
return solveCaptcha(twoCaptchaBaseURL, apiKey, task, "token", false)
|
||||
}
|
||||
|
||||
// ======================================== Anti-Captcha ========================================
|
||||
|
||||
const antiCaptchaBaseURL = "https://api.anti-captcha.com"
|
||||
|
||||
func antiCaptchaSolveRecaptchaV2(apiKey, websiteURL, websiteKey string) (string, error) {
|
||||
return solveCaptcha(antiCaptchaBaseURL, apiKey, map[string]any{
|
||||
"type": "RecaptchaV2TaskProxyless",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
}, "gRecaptchaResponse", false)
|
||||
}
|
||||
|
||||
func antiCaptchaSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction string) (string, error) {
|
||||
// Anti-Captcha requires minScore for reCAPTCHA v3. 0.3 is the loosest threshold.
|
||||
task := map[string]any{
|
||||
"type": "RecaptchaV3TaskProxyless",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
"minScore": 0.3,
|
||||
}
|
||||
if pageAction != "" {
|
||||
task["pageAction"] = pageAction
|
||||
}
|
||||
return solveCaptcha(antiCaptchaBaseURL, apiKey, task, "gRecaptchaResponse", false)
|
||||
}
|
||||
|
||||
func antiCaptchaSolveHCaptcha(apiKey, websiteURL, websiteKey string) (string, error) {
|
||||
// Anti-Captcha returns hCaptcha tokens under "gRecaptchaResponse" (not "token").
|
||||
return solveCaptcha(antiCaptchaBaseURL, apiKey, map[string]any{
|
||||
"type": "HCaptchaTaskProxyless",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
}, "gRecaptchaResponse", false)
|
||||
}
|
||||
|
||||
func antiCaptchaSolveTurnstile(apiKey, websiteURL, websiteKey, cData string) (string, error) {
|
||||
task := map[string]any{
|
||||
"type": "TurnstileTaskProxyless",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
}
|
||||
if cData != "" {
|
||||
task["cData"] = cData
|
||||
}
|
||||
return solveCaptcha(antiCaptchaBaseURL, apiKey, task, "token", false)
|
||||
}
|
||||
|
||||
// ======================================== CapSolver ========================================
|
||||
|
||||
const capSolverBaseURL = "https://api.capsolver.com"
|
||||
|
||||
func capSolverSolveRecaptchaV2(apiKey, websiteURL, websiteKey string) (string, error) {
|
||||
return solveCaptcha(capSolverBaseURL, apiKey, map[string]any{
|
||||
"type": "ReCaptchaV2TaskProxyLess",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
}, "gRecaptchaResponse", true)
|
||||
}
|
||||
|
||||
func capSolverSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction string) (string, error) {
|
||||
task := map[string]any{
|
||||
"type": "ReCaptchaV3TaskProxyLess",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
}
|
||||
if pageAction != "" {
|
||||
task["pageAction"] = pageAction
|
||||
}
|
||||
return solveCaptcha(capSolverBaseURL, apiKey, task, "gRecaptchaResponse", true)
|
||||
}
|
||||
|
||||
func capSolverSolveTurnstile(apiKey, websiteURL, websiteKey, cData string) (string, error) {
|
||||
task := map[string]any{
|
||||
"type": "AntiTurnstileTaskProxyLess",
|
||||
"websiteURL": websiteURL,
|
||||
"websiteKey": websiteKey,
|
||||
}
|
||||
if cData != "" {
|
||||
task["metadata"] = map[string]any{"cdata": cData}
|
||||
}
|
||||
return solveCaptcha(capSolverBaseURL, apiKey, task, "token", true)
|
||||
}
|
||||
@@ -12,10 +12,3 @@ func NewDefaultRandSource() rand.Source {
|
||||
uint64(now>>32),
|
||||
)
|
||||
}
|
||||
|
||||
func firstOrEmpty(values []string) string {
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
return values[0]
|
||||
}
|
||||
|
||||
@@ -574,7 +574,8 @@ func NewDefaultTemplateFuncMap(randSource rand.Source, fileCache *FileCache) tem
|
||||
"fakeit_ErrorHTTP": func() string { return fakeit.ErrorHTTP().Error() },
|
||||
"fakeit_ErrorHTTPClient": func() string { return fakeit.ErrorHTTPClient().Error() },
|
||||
"fakeit_ErrorHTTPServer": func() string { return fakeit.ErrorHTTPServer().Error() },
|
||||
"fakeit_ErrorRuntime": func() string { return fakeit.ErrorRuntime().Error() },
|
||||
// "fakeit_ErrorInput": func() string { return fakeit.ErrorInput().Error() },
|
||||
"fakeit_ErrorRuntime": func() string { return fakeit.ErrorRuntime().Error() },
|
||||
|
||||
// Fakeit / School
|
||||
"fakeit_School": fakeit.School,
|
||||
@@ -584,55 +585,6 @@ func NewDefaultTemplateFuncMap(randSource rand.Source, fileCache *FileCache) tem
|
||||
"fakeit_SongName": fakeit.SongName,
|
||||
"fakeit_SongArtist": fakeit.SongArtist,
|
||||
"fakeit_SongGenre": fakeit.SongGenre,
|
||||
|
||||
// Captcha / 2Captcha
|
||||
// Usage: {{ twocaptcha_RecaptchaV2 "API_KEY" "SITE_KEY" "https://example.com" }}
|
||||
"twocaptcha_RecaptchaV2": func(apiKey, websiteKey, websiteURL string) (string, error) {
|
||||
return twoCaptchaSolveRecaptchaV2(apiKey, websiteURL, websiteKey)
|
||||
},
|
||||
// Usage: {{ twocaptcha_RecaptchaV3 "API_KEY" "SITE_KEY" "https://example.com" "action" }}
|
||||
"twocaptcha_RecaptchaV3": func(apiKey, websiteKey, websiteURL, pageAction string) (string, error) {
|
||||
return twoCaptchaSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction)
|
||||
},
|
||||
// Usage: {{ twocaptcha_Turnstile "API_KEY" "SITE_KEY" "https://example.com" }}
|
||||
// {{ twocaptcha_Turnstile "API_KEY" "SITE_KEY" "https://example.com" "cdata" }}
|
||||
"twocaptcha_Turnstile": func(apiKey, websiteKey, websiteURL string, cData ...string) (string, error) {
|
||||
return twoCaptchaSolveTurnstile(apiKey, websiteURL, websiteKey, firstOrEmpty(cData))
|
||||
},
|
||||
|
||||
// Captcha / Anti-Captcha
|
||||
// Usage: {{ anticaptcha_RecaptchaV2 "API_KEY" "SITE_KEY" "https://example.com" }}
|
||||
"anticaptcha_RecaptchaV2": func(apiKey, websiteKey, websiteURL string) (string, error) {
|
||||
return antiCaptchaSolveRecaptchaV2(apiKey, websiteURL, websiteKey)
|
||||
},
|
||||
// Usage: {{ anticaptcha_RecaptchaV3 "API_KEY" "SITE_KEY" "https://example.com" "action" }}
|
||||
"anticaptcha_RecaptchaV3": func(apiKey, websiteKey, websiteURL, pageAction string) (string, error) {
|
||||
return antiCaptchaSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction)
|
||||
},
|
||||
// Usage: {{ anticaptcha_HCaptcha "API_KEY" "SITE_KEY" "https://example.com" }}
|
||||
"anticaptcha_HCaptcha": func(apiKey, websiteKey, websiteURL string) (string, error) {
|
||||
return antiCaptchaSolveHCaptcha(apiKey, websiteURL, websiteKey)
|
||||
},
|
||||
// Usage: {{ anticaptcha_Turnstile "API_KEY" "SITE_KEY" "https://example.com" }}
|
||||
// {{ anticaptcha_Turnstile "API_KEY" "SITE_KEY" "https://example.com" "cdata" }}
|
||||
"anticaptcha_Turnstile": func(apiKey, websiteKey, websiteURL string, cData ...string) (string, error) {
|
||||
return antiCaptchaSolveTurnstile(apiKey, websiteURL, websiteKey, firstOrEmpty(cData))
|
||||
},
|
||||
|
||||
// Captcha / CapSolver
|
||||
// Usage: {{ capsolver_RecaptchaV2 "API_KEY" "SITE_KEY" "https://example.com" }}
|
||||
"capsolver_RecaptchaV2": func(apiKey, websiteKey, websiteURL string) (string, error) {
|
||||
return capSolverSolveRecaptchaV2(apiKey, websiteURL, websiteKey)
|
||||
},
|
||||
// Usage: {{ capsolver_RecaptchaV3 "API_KEY" "SITE_KEY" "https://example.com" "action" }}
|
||||
"capsolver_RecaptchaV3": func(apiKey, websiteKey, websiteURL, pageAction string) (string, error) {
|
||||
return capSolverSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction)
|
||||
},
|
||||
// Usage: {{ capsolver_Turnstile "API_KEY" "SITE_KEY" "https://example.com" }}
|
||||
// {{ capsolver_Turnstile "API_KEY" "SITE_KEY" "https://example.com" "cdata" }}
|
||||
"capsolver_Turnstile": func(apiKey, websiteKey, websiteURL string, cData ...string) (string, error) {
|
||||
return capSolverSolveTurnstile(apiKey, websiteURL, websiteKey, firstOrEmpty(cData))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -442,65 +442,3 @@ func NewScriptUnknownEngineError(engineType string) ScriptUnknownEngineError {
|
||||
func (e ScriptUnknownEngineError) Error() string {
|
||||
return "unknown engine type: " + e.EngineType
|
||||
}
|
||||
|
||||
// ======================================== Captcha ========================================
|
||||
|
||||
var ErrCaptchaKeyEmpty = errors.New("captcha API key cannot be empty")
|
||||
|
||||
type CaptchaAPIError struct {
|
||||
Endpoint string
|
||||
Code string
|
||||
Description string
|
||||
}
|
||||
|
||||
func NewCaptchaAPIError(endpoint, code, description string) CaptchaAPIError {
|
||||
return CaptchaAPIError{Endpoint: endpoint, Code: code, Description: description}
|
||||
}
|
||||
|
||||
func (e CaptchaAPIError) Error() string {
|
||||
return fmt.Sprintf("captcha %s error: %s (%s)", e.Endpoint, e.Code, e.Description)
|
||||
}
|
||||
|
||||
type CaptchaRequestError struct {
|
||||
Endpoint string
|
||||
Err error
|
||||
}
|
||||
|
||||
func NewCaptchaRequestError(endpoint string, err error) CaptchaRequestError {
|
||||
if err == nil {
|
||||
err = errNoError
|
||||
}
|
||||
return CaptchaRequestError{Endpoint: endpoint, Err: err}
|
||||
}
|
||||
|
||||
func (e CaptchaRequestError) Error() string {
|
||||
return fmt.Sprintf("captcha %s request failed: %v", e.Endpoint, e.Err)
|
||||
}
|
||||
|
||||
func (e CaptchaRequestError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type CaptchaTimeoutError struct {
|
||||
TaskID string
|
||||
}
|
||||
|
||||
func NewCaptchaTimeoutError(taskID string) CaptchaTimeoutError {
|
||||
return CaptchaTimeoutError{TaskID: taskID}
|
||||
}
|
||||
|
||||
func (e CaptchaTimeoutError) Error() string {
|
||||
return fmt.Sprintf("captcha solving timed out (taskId: %s)", e.TaskID)
|
||||
}
|
||||
|
||||
type CaptchaSolutionKeyError struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
func NewCaptchaSolutionKeyError(key string) CaptchaSolutionKeyError {
|
||||
return CaptchaSolutionKeyError{Key: key}
|
||||
}
|
||||
|
||||
func (e CaptchaSolutionKeyError) Error() string {
|
||||
return fmt.Sprintf("captcha solution missing expected key %q", e.Key)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user