Compare commits
	
		
			5 Commits
		
	
	
		
			v0.1
			...
			5a40fb593c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5a40fb593c | |||
| e47a96a597 | |||
| 4d861f037e | |||
| 00b07f7e2b | |||
| 197918a26d | 
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @ -1,6 +1,15 @@ | ||||
| ### sati bridge | ||||
| # sati bridge | ||||
| прослойка, эмулирующая API других сервисов. для использования готового софта с [sati.ac](https://sati.ac) | ||||
|  | ||||
| на данный момент реализованы: | ||||
| - 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" | ||||
| 	"net/http" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.sati.ac/sati.ac/sati-go" | ||||
| 	"github.com/mitchellh/mapstructure" | ||||
| @ -115,19 +116,20 @@ func (a *antigateV2Api) getTaskResult(request struct { | ||||
| 		response.Status = "ready" | ||||
| 		response.EndTime = &task.EndTime | ||||
| 		response.Cost = task.Entity.Cost | ||||
| 		switch task.Result.(type) { | ||||
| 		switch result := task.Result.(type) { | ||||
| 		case *sati.ReCaptcha2Result: | ||||
| 			response.Solution = struct { | ||||
| 				GRecaptchaResponse string `json:"gRecaptchaResponse"` | ||||
| 			}{task.Result.(*sati.ReCaptcha2Result).Token} | ||||
| 			}{result.Token} | ||||
| 		case *sati.TurnstileResult: | ||||
| 			response.Solution = struct { | ||||
| 				Token     string `json:"token"` | ||||
| 				UserAgent string `json:"userAgent"` | ||||
| 			}{ | ||||
| 				task.Result.(*sati.TurnstileResult).Token, | ||||
| 				a.ctx.Config.AntiGateV2.TurnstileUserAgent, | ||||
| 			} | ||||
| 			}{result.Token, a.ctx.Config.AntiGateV2.TurnstileUserAgent} | ||||
| 		case *sati.FunCaptchaResult: | ||||
| 			response.Solution = struct { | ||||
| 				Token string `json:"token"` | ||||
| 			}{result.Token} | ||||
| 		default: | ||||
| 			return errorTaskNotSupported | ||||
| 		} | ||||
| @ -146,8 +148,8 @@ func (a *antigateV2Api) createTask(request struct { | ||||
|  | ||||
| 	var id uint32 | ||||
|  | ||||
| 	switch taskType { | ||||
| 	case "TurnstileTask", "TurnstileTaskProxyless": | ||||
| 	switch strings.ToLower(taskType) { | ||||
| 	case "turnstiletask", "turnstiletaskproxyless": | ||||
| 		var task struct { | ||||
| 			WebsiteURL string  `json:"websiteURL"` | ||||
| 			WebsiteKey string  `json:"websiteKey"` | ||||
| @ -159,7 +161,7 @@ func (a *antigateV2Api) createTask(request struct { | ||||
| 			SiteKey: task.WebsiteKey, | ||||
| 			Action:  task.Action, | ||||
| 		}) | ||||
| 	case "RecaptchaV2Task", "RecaptchaV2TaskProxyless": | ||||
| 	case "recaptchav2task", "recaptchav2taskproxyless": | ||||
| 		var task struct { | ||||
| 			WebsiteURL string `json:"websiteURL"` | ||||
| 			WebsiteKey string `json:"websiteKey"` | ||||
| @ -169,6 +171,18 @@ func (a *antigateV2Api) createTask(request struct { | ||||
| 			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 errorTaskNotSupported | ||||
| 	} | ||||
| @ -196,7 +210,7 @@ func (a *antigateV2Api) getBalance(struct{}) any { | ||||
| 	}{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) | ||||
|  | ||||
| 	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 | ||||
| 		} | ||||
|  | ||||
| 		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) | ||||
| 		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 { | ||||
| 	api := &antigateV2Api{ctx, http.NewServeMux()} | ||||
|  | ||||
| 	api.mux.HandleFunc("/createTask", jsonHandler(api.createTask)) | ||||
| 	api.mux.HandleFunc("/getTaskResult", jsonHandler(api.getTaskResult)) | ||||
| 	api.mux.HandleFunc("/getBalance", jsonHandler(api.getBalance)) | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| @ -72,6 +72,24 @@ type okResponse simpleResponse | ||||
|  | ||||
| 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 { | ||||
| 	var id uint32 | ||||
| 	switch params.Get("method") { | ||||
| @ -125,6 +143,26 @@ func (a *ruCaptchaApi) endpointIn(params url.Values) ruCaptchaResponse { | ||||
| 			Action:  action, | ||||
| 			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: | ||||
| 		return &simpleResponse{0, "ERROR_ZERO_CAPTCHA_FILESIZE"} | ||||
| 	} | ||||
| @ -143,11 +181,13 @@ func (r *get2Response) text() string { | ||||
| } | ||||
|  | ||||
| func (a *ruCaptchaApi) convertTaskResult(task *Task) string { | ||||
| 	switch task.Result.(type) { | ||||
| 	switch result := task.Result.(type) { | ||||
| 	case *sati.ReCaptcha2Result: | ||||
| 		return task.Result.(*sati.ReCaptcha2Result).Token | ||||
| 		return result.Token | ||||
| 	case *sati.TurnstileResult: | ||||
| 		return task.Result.(*sati.TurnstileResult).Token | ||||
| 		return result.Token | ||||
| 	case *sati.FunCaptchaResult: | ||||
| 		return result.Token | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
|  | ||||
| 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 ( | ||||
| 	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-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-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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| 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 | ||||
|  | ||||
| 	hostIp := "127.0.0.1" | ||||
| 	hostIp := strings.SplitN(ctx.Config.Host, ":", 2)[0] | ||||
| 	suffix := "\r\n#sati-bridge start, DO NOT MODIFY\r\n" | ||||
| 	for _, domain := range ctx.Server.GetDomains() { | ||||
| 		suffix += hostIp + " " + domain + "\r\n" | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	