Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
5a40fb593c | |||
e47a96a597 | |||
4d861f037e | |||
00b07f7e2b | |||
197918a26d |
11
README.md
11
README.md
@ -1,6 +1,15 @@
|
|||||||
### sati bridge
|
# sati bridge
|
||||||
прослойка, эмулирующая API других сервисов. для использования готового софта с [sati.ac](https://sati.ac)
|
прослойка, эмулирующая API других сервисов. для использования готового софта с [sati.ac](https://sati.ac)
|
||||||
|
|
||||||
на данный момент реализованы:
|
на данный момент реализованы:
|
||||||
- RuCaptcha
|
- RuCaptcha
|
||||||
- AntiGateV2
|
- AntiGateV2
|
||||||
|
|
||||||
|
## установка и запуск (windows)
|
||||||
|
- скачайте мост со [страницы релизов](https://git.sati.ac/sati.ac/bridge/releases)
|
||||||
|
- создайте новую папку и поместите в неё софт
|
||||||
|
- запустите exeшник от администратора, он закроется, это нормально. рядом должна появиться папка data, с конфигом `config.json` и сертификатом `ca.crt` внутри
|
||||||
|
- далее вам нужно добавить в конфиг ваш API токен: замените `"token": ""` на `"token": "ВАШ_ТОКЕН"`. взять его можно [тут](https://sati.ac/dashboard). если ваш софт использует API схожее с одним из тех, что поддерживаются, но на другом домене, то вы можете добавить его в extraDomains, например так: `{ "example.com": "AntiGateV2" }`
|
||||||
|
- добавьте корневой сертификат в хранилище доверенных корневых центров сертификации, кликнув на него, и выбрав нужное хранилище
|
||||||
|
- снова запустите exeшник с правами администратора, если в логе есть сообщение `starting api server`, то мост успешно запущен
|
||||||
|
- проверьте работу софта, открыв [bridge.sati.ac](https://bridge.sati.ac) в браузере. если все работает - там будет показана статистика
|
@ -5,6 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.sati.ac/sati.ac/sati-go"
|
"git.sati.ac/sati.ac/sati-go"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
@ -115,19 +116,20 @@ func (a *antigateV2Api) getTaskResult(request struct {
|
|||||||
response.Status = "ready"
|
response.Status = "ready"
|
||||||
response.EndTime = &task.EndTime
|
response.EndTime = &task.EndTime
|
||||||
response.Cost = task.Entity.Cost
|
response.Cost = task.Entity.Cost
|
||||||
switch task.Result.(type) {
|
switch result := task.Result.(type) {
|
||||||
case *sati.ReCaptcha2Result:
|
case *sati.ReCaptcha2Result:
|
||||||
response.Solution = struct {
|
response.Solution = struct {
|
||||||
GRecaptchaResponse string `json:"gRecaptchaResponse"`
|
GRecaptchaResponse string `json:"gRecaptchaResponse"`
|
||||||
}{task.Result.(*sati.ReCaptcha2Result).Token}
|
}{result.Token}
|
||||||
case *sati.TurnstileResult:
|
case *sati.TurnstileResult:
|
||||||
response.Solution = struct {
|
response.Solution = struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
UserAgent string `json:"userAgent"`
|
UserAgent string `json:"userAgent"`
|
||||||
}{
|
}{result.Token, a.ctx.Config.AntiGateV2.TurnstileUserAgent}
|
||||||
task.Result.(*sati.TurnstileResult).Token,
|
case *sati.FunCaptchaResult:
|
||||||
a.ctx.Config.AntiGateV2.TurnstileUserAgent,
|
response.Solution = struct {
|
||||||
}
|
Token string `json:"token"`
|
||||||
|
}{result.Token}
|
||||||
default:
|
default:
|
||||||
return errorTaskNotSupported
|
return errorTaskNotSupported
|
||||||
}
|
}
|
||||||
@ -146,8 +148,8 @@ func (a *antigateV2Api) createTask(request struct {
|
|||||||
|
|
||||||
var id uint32
|
var id uint32
|
||||||
|
|
||||||
switch taskType {
|
switch strings.ToLower(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"`
|
||||||
@ -159,7 +161,7 @@ func (a *antigateV2Api) createTask(request struct {
|
|||||||
SiteKey: task.WebsiteKey,
|
SiteKey: task.WebsiteKey,
|
||||||
Action: task.Action,
|
Action: task.Action,
|
||||||
})
|
})
|
||||||
case "RecaptchaV2Task", "RecaptchaV2TaskProxyless":
|
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"`
|
||||||
@ -169,6 +171,18 @@ func (a *antigateV2Api) createTask(request struct {
|
|||||||
PageUrl: task.WebsiteURL,
|
PageUrl: task.WebsiteURL,
|
||||||
SiteKey: task.WebsiteKey,
|
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:
|
default:
|
||||||
return errorTaskNotSupported
|
return errorTaskNotSupported
|
||||||
}
|
}
|
||||||
@ -196,7 +210,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) {
|
||||||
@ -210,7 +224,9 @@ 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)
|
||||||
w.Write(marshaled)
|
w.Write(marshaled)
|
||||||
@ -220,9 +236,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
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,24 @@ type okResponse simpleResponse
|
|||||||
|
|
||||||
func (r *okResponse) text() string { return "OK|" + r.Request }
|
func (r *okResponse) text() string { return "OK|" + r.Request }
|
||||||
|
|
||||||
|
func parsePhpAssociativeArray(values map[string][]string, arrayName string) map[string]string {
|
||||||
|
result := make(map[string]string)
|
||||||
|
prefix := arrayName + "["
|
||||||
|
suffix := "]"
|
||||||
|
|
||||||
|
for key, values := range values {
|
||||||
|
if strings.HasPrefix(key, prefix) && strings.HasSuffix(key, suffix) && len(values) > 0 {
|
||||||
|
result[key[len(prefix):len(key)-len(suffix)]] = values[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
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") {
|
||||||
@ -125,6 +143,26 @@ func (a *ruCaptchaApi) endpointIn(params url.Values) ruCaptchaResponse {
|
|||||||
Action: action,
|
Action: action,
|
||||||
CData: cData,
|
CData: cData,
|
||||||
})
|
})
|
||||||
|
case "funcaptcha":
|
||||||
|
siteKey := params.Get("publickey")
|
||||||
|
pageUrl := params.Get("pageurl")
|
||||||
|
data := parsePhpAssociativeArray(params, "data")
|
||||||
|
if siteKey == "" || pageUrl == "" {
|
||||||
|
return &simpleResponse{0, "ERROR_BAD_PARAMETERS"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceUrl *string
|
||||||
|
if params.Has("surl") {
|
||||||
|
val := params.Get("surl")
|
||||||
|
serviceUrl = &val
|
||||||
|
}
|
||||||
|
|
||||||
|
id = a.ctx.Registry.CreateTask(&sati.FunCaptchaTask{
|
||||||
|
SiteKey: siteKey,
|
||||||
|
PageUrl: pageUrl,
|
||||||
|
ServiceUrl: serviceUrl,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
return &simpleResponse{0, "ERROR_ZERO_CAPTCHA_FILESIZE"}
|
return &simpleResponse{0, "ERROR_ZERO_CAPTCHA_FILESIZE"}
|
||||||
}
|
}
|
||||||
@ -143,11 +181,13 @@ func (r *get2Response) text() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ruCaptchaApi) convertTaskResult(task *Task) string {
|
func (a *ruCaptchaApi) convertTaskResult(task *Task) string {
|
||||||
switch task.Result.(type) {
|
switch result := task.Result.(type) {
|
||||||
case *sati.ReCaptcha2Result:
|
case *sati.ReCaptcha2Result:
|
||||||
return task.Result.(*sati.ReCaptcha2Result).Token
|
return result.Token
|
||||||
case *sati.TurnstileResult:
|
case *sati.TurnstileResult:
|
||||||
return task.Result.(*sati.TurnstileResult).Token
|
return result.Token
|
||||||
|
case *sati.FunCaptchaResult:
|
||||||
|
return result.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
a.ctx.Logger.WithFields(logrus.Fields{
|
a.ctx.Logger.WithFields(logrus.Fields{
|
||||||
|
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-20230630184329-03a405a25122
|
require git.sati.ac/sati.ac/sati-go v0.0.0-20230713145537-57719018ca00
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -4,6 +4,8 @@ git.sati.ac/sati.ac/sati-go v0.0.0-20230629103120-de6961c4f6ec h1:DHmxYp62PfGP+j
|
|||||||
git.sati.ac/sati.ac/sati-go v0.0.0-20230629103120-de6961c4f6ec/go.mod h1:dsLvwV5+2YUjWRAuTYFf/EMvoH/twUu/NWA0t5Yl3pQ=
|
git.sati.ac/sati.ac/sati-go v0.0.0-20230629103120-de6961c4f6ec/go.mod h1:dsLvwV5+2YUjWRAuTYFf/EMvoH/twUu/NWA0t5Yl3pQ=
|
||||||
git.sati.ac/sati.ac/sati-go v0.0.0-20230630184329-03a405a25122 h1:Uff2QZeRDk+3cm3cN9tUiUoT2VoKcwbIf32wYgYSUNU=
|
git.sati.ac/sati.ac/sati-go v0.0.0-20230630184329-03a405a25122 h1:Uff2QZeRDk+3cm3cN9tUiUoT2VoKcwbIf32wYgYSUNU=
|
||||||
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/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=
|
||||||
|
2
main.go
2
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"
|
||||||
|
Reference in New Issue
Block a user