2023-07-02 18:00:59 +02:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2023-07-26 07:08:31 +02:00
|
|
|
"fmt"
|
2023-07-02 18:00:59 +02:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"reflect"
|
2023-07-14 03:05:08 +02:00
|
|
|
"strconv"
|
2023-07-13 22:20:38 +02:00
|
|
|
"strings"
|
2023-07-02 18:00:59 +02:00
|
|
|
|
|
|
|
"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 {
|
2023-07-14 05:49:25 +02:00
|
|
|
return []string{"api.anti-captcha.com"}
|
2023-07-02 18:00:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *antigateV2Api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
a.mux.ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
|
|
|
|
type antigateError struct {
|
2023-07-14 06:15:20 +02:00
|
|
|
ErrorId uint32 `json:"errorId"`
|
2023-07-02 18:00:59 +02:00
|
|
|
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",
|
|
|
|
}
|
2023-07-26 07:08:31 +02:00
|
|
|
|
|
|
|
errorProxyConnectRefused = &antigateError{
|
|
|
|
ErrorId: 25,
|
|
|
|
ErrorCode: "ERROR_PROXY_CONNECT_REFUSED",
|
|
|
|
ErrorDescription: "Bad proxy",
|
|
|
|
}
|
2023-07-02 18:00:59 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func (a *antigateV2Api) getTaskResult(request struct {
|
2023-07-14 03:05:08 +02:00
|
|
|
TaskId any `json:"taskId"`
|
2023-07-02 18:00:59 +02:00
|
|
|
}) any {
|
2023-07-14 03:05:08 +02:00
|
|
|
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)
|
2023-07-02 18:00:59 +02:00
|
|
|
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
|
2023-07-13 18:38:05 +02:00
|
|
|
switch result := task.Result.(type) {
|
2023-07-02 18:00:59 +02:00
|
|
|
case *sati.ReCaptcha2Result:
|
|
|
|
response.Solution = struct {
|
|
|
|
GRecaptchaResponse string `json:"gRecaptchaResponse"`
|
2023-07-13 18:38:05 +02:00
|
|
|
}{result.Token}
|
2023-07-02 18:00:59 +02:00
|
|
|
case *sati.TurnstileResult:
|
|
|
|
response.Solution = struct {
|
|
|
|
Token string `json:"token"`
|
|
|
|
UserAgent string `json:"userAgent"`
|
2023-07-13 18:38:05 +02:00
|
|
|
}{result.Token, a.ctx.Config.AntiGateV2.TurnstileUserAgent}
|
|
|
|
case *sati.FunCaptchaResult:
|
|
|
|
response.Solution = struct {
|
|
|
|
Token string `json:"token"`
|
|
|
|
}{result.Token}
|
2023-07-02 18:00:59 +02:00
|
|
|
default:
|
|
|
|
return errorTaskNotSupported
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
2023-07-26 07:08:31 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-07-02 18:00:59 +02:00
|
|
|
func (a *antigateV2Api) createTask(request struct {
|
|
|
|
Task map[string]any `json:"task"`
|
|
|
|
}) any {
|
|
|
|
taskType, ok := request.Task["type"].(string)
|
|
|
|
if !ok {
|
|
|
|
return errorTaskAbsent
|
|
|
|
}
|
2023-07-26 07:08:31 +02:00
|
|
|
taskType = strings.ToLower(taskType)
|
2023-07-02 18:00:59 +02:00
|
|
|
|
2023-07-26 07:08:31 +02:00
|
|
|
var satiTask sati.AnyTask
|
2023-07-02 18:00:59 +02:00
|
|
|
|
2023-07-26 07:08:31 +02:00
|
|
|
switch taskType {
|
2023-07-13 22:20:38 +02:00
|
|
|
case "turnstiletask", "turnstiletaskproxyless":
|
2023-07-02 18:00:59 +02:00
|
|
|
var task struct {
|
|
|
|
WebsiteURL string `json:"websiteURL"`
|
|
|
|
WebsiteKey string `json:"websiteKey"`
|
|
|
|
Action *string `json:"action"`
|
|
|
|
}
|
|
|
|
mapstructure.Decode(request.Task, &task)
|
2023-07-26 07:08:31 +02:00
|
|
|
satiTask = &sati.TurnstileTask{
|
2023-07-02 18:00:59 +02:00
|
|
|
PageUrl: task.WebsiteURL,
|
|
|
|
SiteKey: task.WebsiteKey,
|
|
|
|
Action: task.Action,
|
2023-07-26 07:08:31 +02:00
|
|
|
}
|
|
|
|
if taskType == "turnstiletask" {
|
|
|
|
proxy := collectAntiGateV2Proxy(request.Task)
|
|
|
|
if proxy == nil {
|
|
|
|
return errorProxyConnectRefused
|
|
|
|
}
|
|
|
|
satiTask.(*sati.TurnstileTask).Proxy = proxy
|
|
|
|
}
|
2023-07-13 22:20:38 +02:00
|
|
|
case "recaptchav2task", "recaptchav2taskproxyless":
|
2023-07-02 18:00:59 +02:00
|
|
|
var task struct {
|
|
|
|
WebsiteURL string `json:"websiteURL"`
|
|
|
|
WebsiteKey string `json:"websiteKey"`
|
|
|
|
}
|
|
|
|
mapstructure.Decode(request.Task, &task)
|
2023-07-26 07:08:31 +02:00
|
|
|
satiTask = &sati.ReCaptcha2Task{
|
2023-07-02 18:00:59 +02:00
|
|
|
PageUrl: task.WebsiteURL,
|
|
|
|
SiteKey: task.WebsiteKey,
|
2023-07-26 07:08:31 +02:00
|
|
|
}
|
|
|
|
if taskType == "funcaptchatask" {
|
|
|
|
proxy := collectAntiGateV2Proxy(request.Task)
|
|
|
|
if proxy == nil {
|
|
|
|
return errorProxyConnectRefused
|
|
|
|
}
|
|
|
|
satiTask.(*sati.ReCaptcha2Task).Proxy = proxy
|
|
|
|
}
|
2023-07-13 22:20:38 +02:00
|
|
|
case "funcaptchatask", "funcaptchataskproxyless":
|
2023-07-13 18:38:05 +02:00
|
|
|
var task struct {
|
|
|
|
WebsiteURL string `json:"websiteURL"`
|
|
|
|
WebsitePublicKey string `json:"websitePublicKey"`
|
|
|
|
Data map[string]string `json:"data"`
|
|
|
|
}
|
|
|
|
mapstructure.Decode(request.Task, &task)
|
2023-07-26 07:08:31 +02:00
|
|
|
satiTask = &sati.FunCaptchaTask{
|
2023-07-13 18:38:05 +02:00
|
|
|
PageUrl: task.WebsiteURL,
|
|
|
|
SiteKey: task.WebsitePublicKey,
|
|
|
|
Data: task.Data,
|
2023-07-26 07:08:31 +02:00
|
|
|
}
|
|
|
|
if taskType == "funcaptchatask" {
|
|
|
|
proxy := collectAntiGateV2Proxy(request.Task)
|
|
|
|
if proxy == nil {
|
|
|
|
return errorProxyConnectRefused
|
|
|
|
}
|
|
|
|
satiTask.(*sati.FunCaptchaTask).Proxy = proxy
|
|
|
|
}
|
2023-08-01 08:15:40 +02:00
|
|
|
case "geetesttask", "geetesttaskproxyless":
|
|
|
|
var task struct {
|
|
|
|
WebsiteURL string `json:"websiteURL"`
|
|
|
|
SiteKey string `json:"gt"`
|
|
|
|
Challenge string `json:"challenge"`
|
|
|
|
ApiServer *string `json:"geetestApiServerSubdomain"`
|
|
|
|
Version *int `json:"version"`
|
|
|
|
}
|
|
|
|
mapstructure.Decode(request.Task, &task)
|
|
|
|
if task.Version != nil && *task.Version != 3 {
|
|
|
|
a.ctx.Logger.WithFields(logrus.Fields{
|
|
|
|
"version": *task.Version,
|
|
|
|
"handler": a.Name(),
|
|
|
|
}).Warn("attempt to solve geetest with bad version")
|
|
|
|
return errorTaskNotSupported
|
|
|
|
}
|
|
|
|
|
|
|
|
satiTask = &sati.GeeTest3Task{
|
|
|
|
SiteKey: task.SiteKey,
|
|
|
|
Challenge: task.Challenge,
|
|
|
|
ApiServer: task.ApiServer,
|
|
|
|
PageUrl: task.WebsiteURL,
|
|
|
|
}
|
|
|
|
if taskType == "geetesttask" {
|
|
|
|
proxy := collectAntiGateV2Proxy(request.Task)
|
|
|
|
if proxy == nil {
|
|
|
|
return errorProxyConnectRefused
|
|
|
|
}
|
|
|
|
satiTask.(*sati.GeeTest3Task).Proxy = proxy
|
|
|
|
}
|
2023-07-02 18:00:59 +02:00
|
|
|
default:
|
|
|
|
return errorTaskNotSupported
|
|
|
|
}
|
|
|
|
|
2023-07-26 07:08:31 +02:00
|
|
|
id := a.ctx.Registry.CreateTask(satiTask)
|
|
|
|
|
2023-07-02 18:00:59 +02:00
|
|
|
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()}
|
|
|
|
}
|
|
|
|
|
2023-07-13 22:19:43 +02:00
|
|
|
func jsonHandler[T any](api *antigateV2Api, handler func(data T) any) func(http.ResponseWriter, *http.Request) {
|
2023-07-02 18:00:59 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-07-13 22:19:43 +02:00
|
|
|
api.ctx.Logger.WithFields(logrus.Fields{"handler": api.Name(), "request": request}).Debug("request")
|
2023-07-02 18:00:59 +02:00
|
|
|
response := handler(request)
|
2023-07-13 22:19:43 +02:00
|
|
|
api.ctx.Logger.WithFields(logrus.Fields{"handler": api.Name(), "response": response}).Debug("response")
|
2023-07-02 18:00:59 +02:00
|
|
|
|
|
|
|
marshaled, _ := json.Marshal(response)
|
2023-07-14 05:49:25 +02:00
|
|
|
|
|
|
|
if _, ok := response.(*antigateError); ok {
|
|
|
|
w.WriteHeader(400)
|
|
|
|
}
|
|
|
|
|
2023-07-02 18:00:59 +02:00
|
|
|
w.Write(marshaled)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newAntigateV2Api(ctx *ApiContext) ApiHandler {
|
|
|
|
api := &antigateV2Api{ctx, http.NewServeMux()}
|
|
|
|
|
2023-07-13 22:19:43 +02:00
|
|
|
api.mux.HandleFunc("/createTask", jsonHandler(api, api.createTask))
|
|
|
|
api.mux.HandleFunc("/getTaskResult", jsonHandler(api, api.getTaskResult))
|
|
|
|
api.mux.HandleFunc("/getBalance", jsonHandler(api, api.getBalance))
|
2023-07-02 18:00:59 +02:00
|
|
|
|
|
|
|
return api
|
|
|
|
}
|