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 capSolverApi struct { ctx *ApiContext mux *http.ServeMux } func (a *capSolverApi) Name() string { return "CapSolver" } func (a *capSolverApi) Domains() []string { return []string{"api.capsolver.com"} } func (a *capSolverApi) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.mux.ServeHTTP(w, r) } type capsolverError struct { ErrorId uint32 `json:"erorrId"` ErrorCode string `json:"errorCode"` ErrorDescription string `json:"errorDescription"` } var ( csErrorInvalidTaskData = &capsolverError{ ErrorId: 1, ErrorCode: "ERROR_INVALID_TASK_DATA", ErrorDescription: "parse request data error", } csErrorCaptchaUnsolvable = &capsolverError{ ErrorId: 1, ErrorCode: "ERROR_CAPTCHA_UNSOLVABLE", ErrorDescription: "Captcha unsolvable", } csErrorTaskNotSupported = &capsolverError{ ErrorId: 1, ErrorCode: "ERROR_TASK_NOT_SUPPORTED", ErrorDescription: "Task type is not supported or typed incorrectly", } csErrorInternal = &capsolverError{ ErrorId: 1, ErrorCode: "ERROR_INTERNAL", ErrorDescription: "Internal error, check bridge logs", } csErrorTaskIdInvalid = &capsolverError{ ErrorId: 1, ErrorCode: "ERROR_TASKID_INVALID", ErrorDescription: "Task ID does not exist or is invalid", } ) func (a *capSolverApi) 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 csErrorTaskIdInvalid } taskId = uint32(parsed) default: return csErrorTaskIdInvalid } task := a.ctx.Registry.Get(taskId) if task == nil { return csErrorTaskIdInvalid } if task.State == StateError { return csErrorCaptchaUnsolvable } response := &struct { ErrorId uint `json:"errorId"` Status string `json:"status"` Solution any `json:"solution"` Cost string `json:"cost"` }{Cost: "0"} if task.State == StateProcessing { response.Status = "processing" } else { response.Status = "ready" 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 { Type string `json:"type"` Token string `json:"token"` UserAgent string `json:"userAgent"` }{"turnstile", result.Token, a.ctx.Config.AntiGateV2.TurnstileUserAgent} case *sati.FunCaptchaResult: response.Solution = struct { Token string `json:"token"` }{result.Token} default: return csErrorTaskNotSupported } } return response } func (a *capSolverApi) createTask(request struct { Task map[string]any `json:"task"` }) any { taskType, ok := request.Task["type"].(string) if !ok { return csErrorInvalidTaskData } var id uint32 switch strings.ToLower(taskType) { case "anticloudflaretask": var task struct { WebsiteURL string `json:"websiteURL"` WebsiteKey string `json:"websiteKey"` Metadata struct { Action *string `json:"action"` CData *string `json:"cdata"` } `json:"metadata"` } mapstructure.Decode(request.Task, &task) id = a.ctx.Registry.CreateTask(&sati.TurnstileTask{ PageUrl: task.WebsiteURL, SiteKey: task.WebsiteKey, Action: task.Metadata.Action, CData: task.Metadata.CData, }) case "recaptchav2task", "recaptchav2taskproxyless": var task struct { WebsiteURL string `json:"websiteURL"` WebsiteKey string `json:"websiteKey"` } mapstructure.Decode(request.Task, &task) id = a.ctx.Registry.CreateTask(&sati.ReCaptcha2Task{ PageUrl: task.WebsiteURL, SiteKey: task.WebsiteKey, }) 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) id = a.ctx.Registry.CreateTask(&sati.FunCaptchaTask{ PageUrl: task.WebsiteURL, SiteKey: task.WebsitePublicKey, Data: task.Data, }) default: return csErrorTaskNotSupported } return &struct { ErrorId uint32 `json:"errorId"` TaskId string `json:"taskId"` }{0, fmt.Sprint(id)} } func (a *capSolverApi) 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 csErrorInternal } return &struct { ErrorId uint `json:"errorId"` Balance float64 `json:"balance"` Packages []struct{} `json:"packages"` }{0, balance.InexactFloat64(), []struct{}{}} } func csJsonHandler[T any](api *capSolverApi, 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(csErrorInvalidTaskData) 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 csError, ok := response.(*capsolverError); ok && csError.ErrorId == 1 { w.WriteHeader(400) } w.Write(marshaled) } } func newCapSolverApi(ctx *ApiContext) ApiHandler { api := &capSolverApi{ctx, http.NewServeMux()} api.mux.HandleFunc("/createTask", csJsonHandler(api, api.createTask)) api.mux.HandleFunc("/getTaskResult", csJsonHandler(api, api.getTaskResult)) api.mux.HandleFunc("/getBalance", csJsonHandler(api, api.getBalance)) return api }