package api import ( "encoding/json" "fmt" "io" "net/http" "reflect" "strconv" "strings" "git.sati.ac/sati.ac/sati-go" "github.com/mitchellh/mapstructure" "github.com/sirupsen/logrus" ) type antigateV2Api struct { ctx *ApiContext mux *http.ServeMux } func (a *antigateV2Api) Name() string { return "AntiGateV2" } func (a *antigateV2Api) Domains() []string { return []string{"api.anti-captcha.com"} } func (a *antigateV2Api) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.mux.ServeHTTP(w, r) } type antigateError struct { ErrorId uint32 `json:"errorId"` ErrorCode string `json:"errorCode"` ErrorDescription string `json:"errorDescription"` } var ( errorNoSuchMethod = &antigateError{ ErrorId: 14, ErrorCode: "ERROR_NO_SUCH_METHOD", ErrorDescription: "Request made to API with a method that does not exist", } errorCaptchaUnsolvable = &antigateError{ ErrorId: 12, ErrorCode: "ERROR_CAPTCHA_UNSOLVABLE", ErrorDescription: "Captcha unsolvable", } errorNoSuchCaptchaId = &antigateError{ ErrorId: 16, ErrorCode: "ERROR_NO_SUCH_CAPCHA_ID", ErrorDescription: "The captcha you are requesting does not exist in your active captchas list or has expired", } errorTaskAbsent = &antigateError{ ErrorId: 22, ErrorCode: "ERROR_TASK_ABSENT", ErrorDescription: `"task" property is empty or not set in the createTask method`, } errorTaskNotSupported = &antigateError{ ErrorId: 23, ErrorCode: "ERROR_TASK_NOT_SUPPORTED", ErrorDescription: "Task type is not supported or typed incorrectly", } errorInternal = &antigateError{ ErrorId: 1000, ErrorCode: "ERROR_INTERNAL", ErrorDescription: "Internal error, check bridge logs", } errorBadRequest = &antigateError{ ErrorId: 1001, ErrorCode: "ERROR_BAD_REQUEST", ErrorDescription: "Bad request", } errorProxyConnectRefused = &antigateError{ ErrorId: 25, ErrorCode: "ERROR_PROXY_CONNECT_REFUSED", ErrorDescription: "Bad proxy", } ) func (a *antigateV2Api) getTaskResult(request struct { TaskId any `json:"taskId"` }) any { var taskId uint32 switch id := request.TaskId.(type) { case float64: taskId = uint32(id) case string: parsed, err := strconv.ParseUint(id, 10, 32) if err != nil { return errorBadRequest } taskId = uint32(parsed) default: return errorBadRequest } task := a.ctx.Registry.Get(taskId) if task == nil { return errorNoSuchCaptchaId } if task.State == StateError { return errorCaptchaUnsolvable } response := &struct { ErrorId uint `json:"errorId"` Status string `json:"status"` Solution any `json:"solution"` Cost string `json:"cost"` Ip string `json:"ip"` CreateTime int64 `json:"createTime"` EndTime *int64 `json:"endTime,omitempty"` SolveCount uint `json:"solveCount"` }{ CreateTime: task.CreateTime, SolveCount: 1, Ip: a.ctx.Config.AntiGateV2.Ip, Cost: "0", } if task.State == StateProcessing { response.Status = "processing" } else { response.Status = "ready" response.EndTime = &task.EndTime response.Cost = task.Entity.Cost switch result := task.Result.(type) { case *sati.ReCaptcha2Result: response.Solution = struct { GRecaptchaResponse string `json:"gRecaptchaResponse"` }{result.Token} case *sati.TurnstileResult: response.Solution = struct { Token string `json:"token"` UserAgent string `json:"userAgent"` }{result.Token, a.ctx.Config.AntiGateV2.TurnstileUserAgent} case *sati.FunCaptchaResult: response.Solution = struct { Token string `json:"token"` }{result.Token} default: return errorTaskNotSupported } } return response } func collectAntiGateV2Proxy(task any) *string { var proxy struct { ProxyType string `json:"proxyType"` ProxyAddress string `json:"proxyAddress"` ProxyPort uint16 `json:"proxyPort"` ProxyLogin *string `json:"proxyLogin"` ProxyPassword *string `json:"proxyPassword"` } if err := mapstructure.Decode(task, proxy); err != nil { return nil } if proxy.ProxyType == "" || proxy.ProxyAddress == "" || proxy.ProxyPort == 0 { return nil } credentials := "" if proxy.ProxyLogin != nil { credentials += *proxy.ProxyLogin if proxy.ProxyPassword != nil { credentials += ":" + *proxy.ProxyPassword } credentials += "@" } proxyStr := fmt.Sprintf("%s://%s%s:%d", proxy.ProxyType, credentials, proxy.ProxyAddress, proxy.ProxyPort) return &proxyStr } func (a *antigateV2Api) createTask(request struct { Task map[string]any `json:"task"` }) any { taskType, ok := request.Task["type"].(string) if !ok { return errorTaskAbsent } taskType = strings.ToLower(taskType) var satiTask sati.AnyTask switch taskType { case "turnstiletask", "turnstiletaskproxyless": var task struct { WebsiteURL string `json:"websiteURL"` WebsiteKey string `json:"websiteKey"` Action *string `json:"action"` } mapstructure.Decode(request.Task, &task) satiTask = &sati.TurnstileTask{ PageUrl: task.WebsiteURL, SiteKey: task.WebsiteKey, Action: task.Action, } if taskType == "turnstiletask" { proxy := collectAntiGateV2Proxy(request.Task) if proxy == nil { return errorProxyConnectRefused } satiTask.(*sati.TurnstileTask).Proxy = proxy } case "recaptchav2task", "recaptchav2taskproxyless": var task struct { WebsiteURL string `json:"websiteURL"` WebsiteKey string `json:"websiteKey"` } mapstructure.Decode(request.Task, &task) satiTask = &sati.ReCaptcha2Task{ PageUrl: task.WebsiteURL, SiteKey: task.WebsiteKey, } if taskType == "funcaptchatask" { proxy := collectAntiGateV2Proxy(request.Task) if proxy == nil { return errorProxyConnectRefused } satiTask.(*sati.ReCaptcha2Task).Proxy = proxy } case "funcaptchatask", "funcaptchataskproxyless": var task struct { WebsiteURL string `json:"websiteURL"` WebsitePublicKey string `json:"websitePublicKey"` Data map[string]string `json:"data"` } mapstructure.Decode(request.Task, &task) satiTask = &sati.FunCaptchaTask{ PageUrl: task.WebsiteURL, SiteKey: task.WebsitePublicKey, Data: task.Data, } if taskType == "funcaptchatask" { proxy := collectAntiGateV2Proxy(request.Task) if proxy == nil { return errorProxyConnectRefused } satiTask.(*sati.FunCaptchaTask).Proxy = proxy } default: return errorTaskNotSupported } id := a.ctx.Registry.CreateTask(satiTask) return &struct { ErrorId uint32 `json:"errorId"` TaskId uint32 `json:"taskId"` }{0, id} } func (a *antigateV2Api) getBalance(struct{}) any { balance, err := a.ctx.Api.GetBalance() if err != nil { a.ctx.Logger.WithFields(logrus.Fields{ "handler": a.Name(), "method": "getBalance", }).Error(err.Error()) return errorInternal } return &struct { ErrorId uint `json:"errorId"` CaptchaCredits uint `json:"captchaCredits"` Balance float64 `json:"balance"` }{0, 0, balance.InexactFloat64()} } func jsonHandler[T any](api *antigateV2Api, handler func(data T) any) func(http.ResponseWriter, *http.Request) { emptyRequest := reflect.Zero(reflect.TypeOf(handler).In(0)).Interface().(T) return func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") data, _ := io.ReadAll(r.Body) request := emptyRequest if err := json.Unmarshal(data, &request); err != nil { marshaled, _ := json.Marshal(errorBadRequest) w.Write(marshaled) return } api.ctx.Logger.WithFields(logrus.Fields{"handler": api.Name(), "request": request}).Debug("request") response := handler(request) api.ctx.Logger.WithFields(logrus.Fields{"handler": api.Name(), "response": response}).Debug("response") marshaled, _ := json.Marshal(response) if _, ok := response.(*antigateError); ok { w.WriteHeader(400) } w.Write(marshaled) } } func newAntigateV2Api(ctx *ApiContext) ApiHandler { api := &antigateV2Api{ctx, http.NewServeMux()} api.mux.HandleFunc("/createTask", jsonHandler(api, api.createTask)) api.mux.HandleFunc("/getTaskResult", jsonHandler(api, api.getTaskResult)) api.mux.HandleFunc("/getBalance", jsonHandler(api, api.getBalance)) return api }