Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
848be2b32b | |||
a9cf77ca52 | |||
8f3d67c648 | |||
bd24760d7d | |||
dd10f15834 | |||
81193a05ef | |||
5f60eb8752 | |||
5a40fb593c | |||
e47a96a597 | |||
4d861f037e |
@ -4,6 +4,7 @@
|
|||||||
на данный момент реализованы:
|
на данный момент реализованы:
|
||||||
- RuCaptcha
|
- RuCaptcha
|
||||||
- AntiGateV2
|
- AntiGateV2
|
||||||
|
- CapSolver
|
||||||
|
|
||||||
## установка и запуск (windows)
|
## установка и запуск (windows)
|
||||||
- скачайте мост со [страницы релизов](https://git.sati.ac/sati.ac/bridge/releases)
|
- скачайте мост со [страницы релизов](https://git.sati.ac/sati.ac/bridge/releases)
|
||||||
|
@ -2,9 +2,12 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.sati.ac/sati.ac/sati-go"
|
"git.sati.ac/sati.ac/sati-go"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
@ -21,10 +24,7 @@ func (a *antigateV2Api) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *antigateV2Api) Domains() []string {
|
func (a *antigateV2Api) Domains() []string {
|
||||||
return []string{
|
return []string{"api.anti-captcha.com"}
|
||||||
"api.anti-captcha.com",
|
|
||||||
"api.capsolver.com",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *antigateV2Api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (a *antigateV2Api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -32,7 +32,7 @@ func (a *antigateV2Api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type antigateError struct {
|
type antigateError struct {
|
||||||
ErrorId uint32 `json:"erorrId"`
|
ErrorId uint32 `json:"errorId"`
|
||||||
ErrorCode string `json:"errorCode"`
|
ErrorCode string `json:"errorCode"`
|
||||||
ErrorDescription string `json:"errorDescription"`
|
ErrorDescription string `json:"errorDescription"`
|
||||||
}
|
}
|
||||||
@ -79,12 +79,32 @@ var (
|
|||||||
ErrorCode: "ERROR_BAD_REQUEST",
|
ErrorCode: "ERROR_BAD_REQUEST",
|
||||||
ErrorDescription: "Bad request",
|
ErrorDescription: "Bad request",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorProxyConnectRefused = &antigateError{
|
||||||
|
ErrorId: 25,
|
||||||
|
ErrorCode: "ERROR_PROXY_CONNECT_REFUSED",
|
||||||
|
ErrorDescription: "Bad proxy",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *antigateV2Api) getTaskResult(request struct {
|
func (a *antigateV2Api) getTaskResult(request struct {
|
||||||
TaskId uint32 `json:"taskId"`
|
TaskId any `json:"taskId"`
|
||||||
}) any {
|
}) any {
|
||||||
task := a.ctx.Registry.Get(request.TaskId)
|
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 {
|
if task == nil {
|
||||||
return errorNoSuchCaptchaId
|
return errorNoSuchCaptchaId
|
||||||
}
|
}
|
||||||
@ -137,6 +157,35 @@ func (a *antigateV2Api) getTaskResult(request struct {
|
|||||||
return response
|
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 {
|
func (a *antigateV2Api) createTask(request struct {
|
||||||
Task map[string]any `json:"task"`
|
Task map[string]any `json:"task"`
|
||||||
}) any {
|
}) any {
|
||||||
@ -144,48 +193,102 @@ func (a *antigateV2Api) createTask(request struct {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return errorTaskAbsent
|
return errorTaskAbsent
|
||||||
}
|
}
|
||||||
|
taskType = strings.ToLower(taskType)
|
||||||
|
|
||||||
var id uint32
|
var satiTask sati.AnyTask
|
||||||
|
|
||||||
switch taskType {
|
switch taskType {
|
||||||
case "TurnstileTask", "TurnstileTaskProxyless":
|
case "turnstiletask", "turnstiletaskproxyless":
|
||||||
var task struct {
|
var task struct {
|
||||||
WebsiteURL string `json:"websiteURL"`
|
WebsiteURL string `json:"websiteURL"`
|
||||||
WebsiteKey string `json:"websiteKey"`
|
WebsiteKey string `json:"websiteKey"`
|
||||||
Action *string `json:"action"`
|
Action *string `json:"action"`
|
||||||
}
|
}
|
||||||
mapstructure.Decode(request.Task, &task)
|
mapstructure.Decode(request.Task, &task)
|
||||||
id = a.ctx.Registry.CreateTask(&sati.TurnstileTask{
|
satiTask = &sati.TurnstileTask{
|
||||||
PageUrl: task.WebsiteURL,
|
PageUrl: task.WebsiteURL,
|
||||||
SiteKey: task.WebsiteKey,
|
SiteKey: task.WebsiteKey,
|
||||||
Action: task.Action,
|
Action: task.Action,
|
||||||
})
|
}
|
||||||
case "RecaptchaV2Task", "RecaptchaV2TaskProxyless":
|
if taskType == "turnstiletask" {
|
||||||
|
proxy := collectAntiGateV2Proxy(request.Task)
|
||||||
|
if proxy == nil {
|
||||||
|
return errorProxyConnectRefused
|
||||||
|
}
|
||||||
|
satiTask.(*sati.TurnstileTask).Proxy = proxy
|
||||||
|
}
|
||||||
|
case "recaptchav2task", "recaptchav2taskproxyless":
|
||||||
var task struct {
|
var task struct {
|
||||||
WebsiteURL string `json:"websiteURL"`
|
WebsiteURL string `json:"websiteURL"`
|
||||||
WebsiteKey string `json:"websiteKey"`
|
WebsiteKey string `json:"websiteKey"`
|
||||||
}
|
}
|
||||||
mapstructure.Decode(request.Task, &task)
|
mapstructure.Decode(request.Task, &task)
|
||||||
id = a.ctx.Registry.CreateTask(&sati.ReCaptcha2Task{
|
satiTask = &sati.ReCaptcha2Task{
|
||||||
PageUrl: task.WebsiteURL,
|
PageUrl: task.WebsiteURL,
|
||||||
SiteKey: task.WebsiteKey,
|
SiteKey: task.WebsiteKey,
|
||||||
})
|
}
|
||||||
case "FunCaptchaTask", "FunCaptchaTaskProxyless":
|
if taskType == "funcaptchatask" {
|
||||||
|
proxy := collectAntiGateV2Proxy(request.Task)
|
||||||
|
if proxy == nil {
|
||||||
|
return errorProxyConnectRefused
|
||||||
|
}
|
||||||
|
satiTask.(*sati.ReCaptcha2Task).Proxy = proxy
|
||||||
|
}
|
||||||
|
case "funcaptchatask", "funcaptchataskproxyless":
|
||||||
var task struct {
|
var task struct {
|
||||||
WebsiteURL string `json:"websiteURL"`
|
WebsiteURL string `json:"websiteURL"`
|
||||||
WebsitePublicKey string `json:"websitePublicKey"`
|
WebsitePublicKey string `json:"websitePublicKey"`
|
||||||
Data map[string]string `json:"data"`
|
Data map[string]string `json:"data"`
|
||||||
}
|
}
|
||||||
mapstructure.Decode(request.Task, &task)
|
mapstructure.Decode(request.Task, &task)
|
||||||
id = a.ctx.Registry.CreateTask(&sati.FunCaptchaTask{
|
satiTask = &sati.FunCaptchaTask{
|
||||||
PageUrl: task.WebsiteURL,
|
PageUrl: task.WebsiteURL,
|
||||||
SiteKey: task.WebsitePublicKey,
|
SiteKey: task.WebsitePublicKey,
|
||||||
Data: task.Data,
|
Data: task.Data,
|
||||||
})
|
}
|
||||||
|
if taskType == "funcaptchatask" {
|
||||||
|
proxy := collectAntiGateV2Proxy(request.Task)
|
||||||
|
if proxy == nil {
|
||||||
|
return errorProxyConnectRefused
|
||||||
|
}
|
||||||
|
satiTask.(*sati.FunCaptchaTask).Proxy = proxy
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return errorTaskNotSupported
|
return errorTaskNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id := a.ctx.Registry.CreateTask(satiTask)
|
||||||
|
|
||||||
return &struct {
|
return &struct {
|
||||||
ErrorId uint32 `json:"errorId"`
|
ErrorId uint32 `json:"errorId"`
|
||||||
TaskId uint32 `json:"taskId"`
|
TaskId uint32 `json:"taskId"`
|
||||||
@ -209,7 +312,7 @@ func (a *antigateV2Api) getBalance(struct{}) any {
|
|||||||
}{0, 0, balance.InexactFloat64()}
|
}{0, 0, balance.InexactFloat64()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonHandler[T any](handler func(data T) any) func(http.ResponseWriter, *http.Request) {
|
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)
|
emptyRequest := reflect.Zero(reflect.TypeOf(handler).In(0)).Interface().(T)
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -223,9 +326,16 @@ func jsonHandler[T any](handler func(data T) any) func(http.ResponseWriter, *htt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api.ctx.Logger.WithFields(logrus.Fields{"handler": api.Name(), "request": request}).Debug("request")
|
||||||
response := handler(request)
|
response := handler(request)
|
||||||
|
api.ctx.Logger.WithFields(logrus.Fields{"handler": api.Name(), "response": response}).Debug("response")
|
||||||
|
|
||||||
marshaled, _ := json.Marshal(response)
|
marshaled, _ := json.Marshal(response)
|
||||||
|
|
||||||
|
if _, ok := response.(*antigateError); ok {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
}
|
||||||
|
|
||||||
w.Write(marshaled)
|
w.Write(marshaled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,9 +343,9 @@ func jsonHandler[T any](handler func(data T) any) func(http.ResponseWriter, *htt
|
|||||||
func newAntigateV2Api(ctx *ApiContext) ApiHandler {
|
func newAntigateV2Api(ctx *ApiContext) ApiHandler {
|
||||||
api := &antigateV2Api{ctx, http.NewServeMux()}
|
api := &antigateV2Api{ctx, http.NewServeMux()}
|
||||||
|
|
||||||
api.mux.HandleFunc("/createTask", jsonHandler(api.createTask))
|
api.mux.HandleFunc("/createTask", jsonHandler(api, api.createTask))
|
||||||
api.mux.HandleFunc("/getTaskResult", jsonHandler(api.getTaskResult))
|
api.mux.HandleFunc("/getTaskResult", jsonHandler(api, api.getTaskResult))
|
||||||
api.mux.HandleFunc("/getBalance", jsonHandler(api.getBalance))
|
api.mux.HandleFunc("/getBalance", jsonHandler(api, api.getBalance))
|
||||||
|
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
334
api/CapSolver.go
Normal file
334
api/CapSolver.go
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"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:"errorId"`
|
||||||
|
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 collectCapSolverProxy(task any) *string {
|
||||||
|
antigateLike := collectAntiGateV2Proxy(task)
|
||||||
|
if antigateLike != nil {
|
||||||
|
return antigateLike
|
||||||
|
}
|
||||||
|
|
||||||
|
input, _ := task.(map[string]any)["proxy"].(string)
|
||||||
|
if input == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isUrl := strings.Contains(input, "://")
|
||||||
|
if isUrl {
|
||||||
|
// validate it
|
||||||
|
url, err := url.Parse(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
output := url.String()
|
||||||
|
return &output
|
||||||
|
}
|
||||||
|
|
||||||
|
fragments := strings.Split(input, ":")
|
||||||
|
var output string
|
||||||
|
switch len(fragments) {
|
||||||
|
case 5: // proto:host:port:user:pwd
|
||||||
|
output = fmt.Sprintf("%s://%s:%s@%s:%s", fragments[0], fragments[3], fragments[4], fragments[1], fragments[2])
|
||||||
|
case 4: // host:port:user:pwd, http used by default
|
||||||
|
output = fmt.Sprintf("http://%s:%s@%s:%s", fragments[2], fragments[3], fragments[0], fragments[1])
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *capSolverApi) createTask(request struct {
|
||||||
|
Task map[string]any `json:"task"`
|
||||||
|
}) any {
|
||||||
|
taskType, ok := request.Task["type"].(string)
|
||||||
|
if !ok {
|
||||||
|
return csErrorInvalidTaskData
|
||||||
|
}
|
||||||
|
|
||||||
|
var satiTask sati.AnyTask
|
||||||
|
|
||||||
|
taskType = strings.ToLower(taskType)
|
||||||
|
switch 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)
|
||||||
|
|
||||||
|
proxy := collectCapSolverProxy(request.Task)
|
||||||
|
if proxy == nil {
|
||||||
|
return csErrorInvalidTaskData
|
||||||
|
}
|
||||||
|
|
||||||
|
satiTask = &sati.TurnstileTask{
|
||||||
|
PageUrl: task.WebsiteURL,
|
||||||
|
SiteKey: task.WebsiteKey,
|
||||||
|
Action: task.Metadata.Action,
|
||||||
|
CData: task.Metadata.CData,
|
||||||
|
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 == "recaptchav2task" {
|
||||||
|
proxy := collectCapSolverProxy(request.Task)
|
||||||
|
if proxy == nil {
|
||||||
|
return csErrorInvalidTaskData
|
||||||
|
}
|
||||||
|
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 := collectCapSolverProxy(request.Task)
|
||||||
|
if proxy == nil {
|
||||||
|
return csErrorInvalidTaskData
|
||||||
|
}
|
||||||
|
satiTask.(*sati.FunCaptchaTask).Proxy = proxy
|
||||||
|
}
|
||||||
|
case "geetesttask", "geetesttaskproxyless":
|
||||||
|
var task struct {
|
||||||
|
WebsiteURL string `json:"websiteURL"`
|
||||||
|
SiteKey string `json:"gt"`
|
||||||
|
Challenge string `json:"challenge"`
|
||||||
|
CaptchaId *string `json:"captchaId"`
|
||||||
|
ApiServer *string `json:"geetestApiServerSubdomain"`
|
||||||
|
}
|
||||||
|
mapstructure.Decode(request.Task, &task)
|
||||||
|
if task.CaptchaId != nil {
|
||||||
|
a.ctx.Logger.WithField("handler", a.Name()).
|
||||||
|
Warn("attempt to solve geetest v4")
|
||||||
|
return csErrorTaskNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
satiTask = &sati.GeeTest3Task{
|
||||||
|
SiteKey: task.SiteKey,
|
||||||
|
Challenge: task.Challenge,
|
||||||
|
ApiServer: task.ApiServer,
|
||||||
|
PageUrl: task.WebsiteURL,
|
||||||
|
}
|
||||||
|
if taskType == "geetesttask" {
|
||||||
|
proxy := collectCapSolverProxy(request.Task)
|
||||||
|
if proxy == nil {
|
||||||
|
return csErrorInvalidTaskData
|
||||||
|
}
|
||||||
|
satiTask.(*sati.GeeTest3Task).Proxy = proxy
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return csErrorTaskNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
id := a.ctx.Registry.CreateTask(satiTask)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -36,6 +37,27 @@ type ruCaptchaResponse interface{ text() string }
|
|||||||
func (a *ruCaptchaApi) wrap(handler func(url.Values) ruCaptchaResponse) func(http.ResponseWriter, *http.Request) {
|
func (a *ruCaptchaApi) wrap(handler func(url.Values) ruCaptchaResponse) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
|
if r.Method != "GET" {
|
||||||
|
var err error
|
||||||
|
switch strings.ToLower(r.Header.Get("Content-Type")) {
|
||||||
|
case "application/x-www-form-urlencoded":
|
||||||
|
err = r.ParseForm()
|
||||||
|
case "multipart/form-data":
|
||||||
|
err = r.ParseMultipartForm(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// merge form with url params
|
||||||
|
for key, values := range r.Form {
|
||||||
|
if _, ok := query[key]; ok {
|
||||||
|
query[key] = append(query[key], values...)
|
||||||
|
} else {
|
||||||
|
query[key] = values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useJson := false
|
useJson := false
|
||||||
if val := query.Get("json"); val != "" && val != "0" {
|
if val := query.Get("json"); val != "" && val != "0" {
|
||||||
useJson = true
|
useJson = true
|
||||||
@ -90,6 +112,21 @@ func parsePhpAssociativeArray(values map[string][]string, arrayName string) map[
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractRuCaptchaProxy(params url.Values) *string {
|
||||||
|
if !params.Has("proxytype") || !params.Has("proxy") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyUrl := fmt.Sprintf("%s://%s", params.Get("proxytype"), params.Get("proxy"))
|
||||||
|
url, err := url.Parse(proxyUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted := url.String()
|
||||||
|
return &formatted
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ruCaptchaApi) endpointIn(params url.Values) ruCaptchaResponse {
|
func (a *ruCaptchaApi) endpointIn(params url.Values) ruCaptchaResponse {
|
||||||
var id uint32
|
var id uint32
|
||||||
switch params.Get("method") {
|
switch params.Get("method") {
|
||||||
@ -118,6 +155,7 @@ func (a *ruCaptchaApi) endpointIn(params url.Values) ruCaptchaResponse {
|
|||||||
id = a.ctx.Registry.CreateTask(&sati.ReCaptcha2Task{
|
id = a.ctx.Registry.CreateTask(&sati.ReCaptcha2Task{
|
||||||
PageUrl: pageUrl,
|
PageUrl: pageUrl,
|
||||||
SiteKey: siteKey,
|
SiteKey: siteKey,
|
||||||
|
Proxy: extractRuCaptchaProxy(params),
|
||||||
})
|
})
|
||||||
case "turnstile":
|
case "turnstile":
|
||||||
pageUrl := params.Get("pageurl")
|
pageUrl := params.Get("pageurl")
|
||||||
@ -142,6 +180,7 @@ func (a *ruCaptchaApi) endpointIn(params url.Values) ruCaptchaResponse {
|
|||||||
PageUrl: pageUrl,
|
PageUrl: pageUrl,
|
||||||
Action: action,
|
Action: action,
|
||||||
CData: cData,
|
CData: cData,
|
||||||
|
Proxy: extractRuCaptchaProxy(params),
|
||||||
})
|
})
|
||||||
case "funcaptcha":
|
case "funcaptcha":
|
||||||
siteKey := params.Get("publickey")
|
siteKey := params.Get("publickey")
|
||||||
@ -162,7 +201,25 @@ func (a *ruCaptchaApi) endpointIn(params url.Values) ruCaptchaResponse {
|
|||||||
PageUrl: pageUrl,
|
PageUrl: pageUrl,
|
||||||
ServiceUrl: serviceUrl,
|
ServiceUrl: serviceUrl,
|
||||||
Data: data,
|
Data: data,
|
||||||
|
Proxy: extractRuCaptchaProxy(params),
|
||||||
})
|
})
|
||||||
|
case "geetest":
|
||||||
|
task := &sati.GeeTest3Task{
|
||||||
|
Proxy: extractRuCaptchaProxy(params),
|
||||||
|
SiteKey: params.Get("gt"),
|
||||||
|
PageUrl: params.Get("pageurl"),
|
||||||
|
Challenge: params.Get("challenge"),
|
||||||
|
}
|
||||||
|
if task.SiteKey == "" || task.PageUrl == "" || task.Challenge == "" {
|
||||||
|
return &simpleResponse{0, "ERROR_BAD_PARAMETERS"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Has("api_server") {
|
||||||
|
apiServer := params.Get("api_server")
|
||||||
|
task.ApiServer = &apiServer
|
||||||
|
}
|
||||||
|
|
||||||
|
id = a.ctx.Registry.CreateTask(task)
|
||||||
default:
|
default:
|
||||||
return &simpleResponse{0, "ERROR_ZERO_CAPTCHA_FILESIZE"}
|
return &simpleResponse{0, "ERROR_ZERO_CAPTCHA_FILESIZE"}
|
||||||
}
|
}
|
||||||
@ -188,6 +245,14 @@ func (a *ruCaptchaApi) convertTaskResult(task *Task) string {
|
|||||||
return result.Token
|
return result.Token
|
||||||
case *sati.FunCaptchaResult:
|
case *sati.FunCaptchaResult:
|
||||||
return result.Token
|
return result.Token
|
||||||
|
case *sati.GeeTest3Result:
|
||||||
|
data, _ := json.Marshal(&struct {
|
||||||
|
Challenge string `json:"geetest_challenge"`
|
||||||
|
Validate string `json:"geetest_validate"`
|
||||||
|
Seccode string `json:"geetest_seccode"`
|
||||||
|
}{result.Challenge, result.Validate, result.Seccode})
|
||||||
|
|
||||||
|
return string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.ctx.Logger.WithFields(logrus.Fields{
|
a.ctx.Logger.WithFields(logrus.Fields{
|
||||||
|
13
api/api.go
13
api/api.go
@ -35,19 +35,19 @@ type RegistryStats struct {
|
|||||||
|
|
||||||
type TaskRegistry struct {
|
type TaskRegistry struct {
|
||||||
mu *sync.RWMutex
|
mu *sync.RWMutex
|
||||||
lifetime time.Duration
|
config *config.Config
|
||||||
tasks map[uint32]*Task
|
tasks map[uint32]*Task
|
||||||
idCounter uint32
|
idCounter uint32
|
||||||
api *sati.Api
|
api *sati.Api
|
||||||
stats RegistryStats
|
stats RegistryStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTaskRegistry(api *sati.Api, lifetime time.Duration) *TaskRegistry {
|
func NewTaskRegistry(api *sati.Api, config *config.Config) *TaskRegistry {
|
||||||
return &TaskRegistry{
|
return &TaskRegistry{
|
||||||
mu: &sync.RWMutex{},
|
mu: &sync.RWMutex{},
|
||||||
tasks: map[uint32]*Task{},
|
tasks: map[uint32]*Task{},
|
||||||
api: api,
|
api: api,
|
||||||
lifetime: lifetime,
|
config: config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ func (t *TaskRegistry) CreateTask(task sati.AnyTask) uint32 {
|
|||||||
entry.EndTime = time.Now().Unix()
|
entry.EndTime = time.Now().Unix()
|
||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
|
|
||||||
time.Sleep(t.lifetime)
|
time.Sleep(time.Millisecond * time.Duration(t.config.TaskLifetime))
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
delete(t.tasks, id)
|
delete(t.tasks, id)
|
||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
@ -98,6 +98,10 @@ func (t *TaskRegistry) Stats() RegistryStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TaskRegistry) Get(id uint32) *Task {
|
func (t *TaskRegistry) Get(id uint32) *Task {
|
||||||
|
if t.config.TaskGetDelay != 0 {
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(t.config.TaskGetDelay))
|
||||||
|
}
|
||||||
|
|
||||||
t.mu.RLock()
|
t.mu.RLock()
|
||||||
defer t.mu.RUnlock()
|
defer t.mu.RUnlock()
|
||||||
task := t.tasks[id]
|
task := t.tasks[id]
|
||||||
@ -162,6 +166,7 @@ func NewApiServer(ctx *ApiContext) *ApiServer {
|
|||||||
newStatsApi(ctx),
|
newStatsApi(ctx),
|
||||||
newAntigateV2Api(ctx),
|
newAntigateV2Api(ctx),
|
||||||
newRuCaptchaApi(ctx),
|
newRuCaptchaApi(ctx),
|
||||||
|
newCapSolverApi(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
server := &ApiServer{
|
server := &ApiServer{
|
||||||
|
@ -16,6 +16,8 @@ type Config struct {
|
|||||||
TlsKeyPath string `json:"tlsKeyPath"`
|
TlsKeyPath string `json:"tlsKeyPath"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
TlsHost string `json:"tlsHost"`
|
TlsHost string `json:"tlsHost"`
|
||||||
|
TaskLifetime int64 `json:"taskLifetime"`
|
||||||
|
TaskGetDelay int64 `json:"taskGetDelay"`
|
||||||
AntiGateV2 struct {
|
AntiGateV2 struct {
|
||||||
TurnstileUserAgent string `json:"turnstileUserAgent"`
|
TurnstileUserAgent string `json:"turnstileUserAgent"`
|
||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
@ -32,6 +34,8 @@ func Default() *Config {
|
|||||||
TlsKeyPath: "./data/ca.key",
|
TlsKeyPath: "./data/ca.key",
|
||||||
Host: "127.0.0.1:80",
|
Host: "127.0.0.1:80",
|
||||||
TlsHost: "127.0.0.1:443",
|
TlsHost: "127.0.0.1:443",
|
||||||
|
TaskLifetime: 60000,
|
||||||
|
TaskGetDelay: 0,
|
||||||
AntiGateV2: struct {
|
AntiGateV2: struct {
|
||||||
TurnstileUserAgent string `json:"turnstileUserAgent"`
|
TurnstileUserAgent string `json:"turnstileUserAgent"`
|
||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
2
go.mod
2
go.mod
@ -2,7 +2,7 @@ module git.sati.ac/sati.ac/bridge
|
|||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require git.sati.ac/sati.ac/sati-go v0.0.0-20230713145537-57719018ca00
|
require git.sati.ac/sati.ac/sati-go v0.0.0-20230801043632-286e5aa6e5c9
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -6,6 +6,10 @@ git.sati.ac/sati.ac/sati-go v0.0.0-20230630184329-03a405a25122 h1:Uff2QZeRDk+3cm
|
|||||||
git.sati.ac/sati.ac/sati-go v0.0.0-20230630184329-03a405a25122/go.mod h1:dsLvwV5+2YUjWRAuTYFf/EMvoH/twUu/NWA0t5Yl3pQ=
|
git.sati.ac/sati.ac/sati-go v0.0.0-20230630184329-03a405a25122/go.mod h1:dsLvwV5+2YUjWRAuTYFf/EMvoH/twUu/NWA0t5Yl3pQ=
|
||||||
git.sati.ac/sati.ac/sati-go v0.0.0-20230713145537-57719018ca00 h1:emjsk5AubG3EiCbiG0WAG6xAZXoNNwO/yteYj/J0TqA=
|
git.sati.ac/sati.ac/sati-go v0.0.0-20230713145537-57719018ca00 h1:emjsk5AubG3EiCbiG0WAG6xAZXoNNwO/yteYj/J0TqA=
|
||||||
git.sati.ac/sati.ac/sati-go v0.0.0-20230713145537-57719018ca00/go.mod h1:dsLvwV5+2YUjWRAuTYFf/EMvoH/twUu/NWA0t5Yl3pQ=
|
git.sati.ac/sati.ac/sati-go v0.0.0-20230713145537-57719018ca00/go.mod h1:dsLvwV5+2YUjWRAuTYFf/EMvoH/twUu/NWA0t5Yl3pQ=
|
||||||
|
git.sati.ac/sati.ac/sati-go v0.0.0-20230725102846-8e6b00348696 h1:ld33XeJOBd1skmlVz/TQrcuZPaTIuBt+J1n52oRfPAo=
|
||||||
|
git.sati.ac/sati.ac/sati-go v0.0.0-20230725102846-8e6b00348696/go.mod h1:dsLvwV5+2YUjWRAuTYFf/EMvoH/twUu/NWA0t5Yl3pQ=
|
||||||
|
git.sati.ac/sati.ac/sati-go v0.0.0-20230801043632-286e5aa6e5c9 h1:8BOOkuFUf3l9vJYc0bt+eWN7VAEZAVhbdiCBhdWtL0A=
|
||||||
|
git.sati.ac/sati.ac/sati-go v0.0.0-20230801043632-286e5aa6e5c9/go.mod h1:dsLvwV5+2YUjWRAuTYFf/EMvoH/twUu/NWA0t5Yl3pQ=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
4
main.go
4
main.go
@ -207,7 +207,7 @@ func addDomainsToHosts(ctx *api.ApiContext) error {
|
|||||||
|
|
||||||
hosts = hostsModRE.ReplaceAll(hosts, []byte{}) // remove old entries
|
hosts = hostsModRE.ReplaceAll(hosts, []byte{}) // remove old entries
|
||||||
|
|
||||||
hostIp := "127.0.0.1"
|
hostIp := strings.SplitN(ctx.Config.Host, ":", 2)[0]
|
||||||
suffix := "\r\n#sati-bridge start, DO NOT MODIFY\r\n"
|
suffix := "\r\n#sati-bridge start, DO NOT MODIFY\r\n"
|
||||||
for _, domain := range ctx.Server.GetDomains() {
|
for _, domain := range ctx.Server.GetDomains() {
|
||||||
suffix += hostIp + " " + domain + "\r\n"
|
suffix += hostIp + " " + domain + "\r\n"
|
||||||
@ -289,7 +289,7 @@ func main() {
|
|||||||
satiConfig.Debug = cfg.Debug
|
satiConfig.Debug = cfg.Debug
|
||||||
satiApi := sati.NewApi(satiConfig)
|
satiApi := sati.NewApi(satiConfig)
|
||||||
|
|
||||||
registry := api.NewTaskRegistry(satiApi, time.Minute)
|
registry := api.NewTaskRegistry(satiApi, cfg)
|
||||||
|
|
||||||
ctx := api.ApiContext{
|
ctx := api.ApiContext{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user