mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 13:14:11 +00:00
- Update go.mod module path from mhsanaei/3x-ui/v3 to saeederamy/3x-ui/v3 - Update all 73 Go files' import paths accordingly - Fix README.fa_IR.md install command to point to fork's main branch The fork was referencing the original repo's module path in go.mod and all Go source imports, making it dependent on MHSanaei's namespace at build time. https://claude.ai/code/session_01M6d5atbWjuLTj6UwRHoK5m
150 lines
3.5 KiB
Go
150 lines
3.5 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/saeederamy/3x-ui/v3/util/common"
|
|
)
|
|
|
|
type NordService struct {
|
|
SettingService
|
|
}
|
|
|
|
var nordHTTPClient = &http.Client{Timeout: 15 * time.Second}
|
|
|
|
// maxResponseSize limits the maximum size of NordVPN API responses (10 MB).
|
|
const maxResponseSize = 10 << 20
|
|
|
|
func (s *NordService) GetCountries() (string, error) {
|
|
resp, err := nordHTTPClient.Get("https://api.nordvpn.com/v1/countries")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
|
|
}
|
|
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(body), nil
|
|
}
|
|
|
|
func (s *NordService) GetServers(countryId string) (string, error) {
|
|
// Validate countryId is numeric to prevent URL injection
|
|
for _, c := range countryId {
|
|
if c < '0' || c > '9' {
|
|
return "", common.NewError("invalid country ID")
|
|
}
|
|
}
|
|
url := fmt.Sprintf("https://api.nordvpn.com/v2/servers?limit=0&filters[servers_technologies][id]=35&filters[country_id]=%s", countryId)
|
|
resp, err := nordHTTPClient.Get(url)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
|
|
}
|
|
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var data map[string]any
|
|
if err := json.Unmarshal(body, &data); err != nil {
|
|
return string(body), nil
|
|
}
|
|
|
|
servers, ok := data["servers"].([]any)
|
|
if !ok {
|
|
return string(body), nil
|
|
}
|
|
|
|
var filtered []any
|
|
for _, s := range servers {
|
|
if server, ok := s.(map[string]any); ok {
|
|
if load, ok := server["load"].(float64); ok && load > 7 {
|
|
filtered = append(filtered, s)
|
|
}
|
|
}
|
|
}
|
|
data["servers"] = filtered
|
|
|
|
result, _ := json.Marshal(data)
|
|
return string(result), nil
|
|
}
|
|
|
|
func (s *NordService) SetKey(privateKey string) (string, error) {
|
|
if privateKey == "" {
|
|
return "", common.NewError("private key cannot be empty")
|
|
}
|
|
nordData := map[string]string{
|
|
"private_key": privateKey,
|
|
"token": "",
|
|
}
|
|
data, _ := json.Marshal(nordData)
|
|
err := s.SettingService.SetNord(string(data))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func (s *NordService) GetCredentials(token string) (string, error) {
|
|
url := "https://api.nordvpn.com/v1/users/services/credentials"
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
req.SetBasicAuth("token", token)
|
|
|
|
resp, err := nordHTTPClient.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
|
|
}
|
|
|
|
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var creds map[string]any
|
|
if err := json.Unmarshal(body, &creds); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
privateKey, ok := creds["nordlynx_private_key"].(string)
|
|
if !ok || privateKey == "" {
|
|
return "", common.NewError("failed to retrieve NordLynx private key")
|
|
}
|
|
|
|
nordData := map[string]string{
|
|
"private_key": privateKey,
|
|
"token": token,
|
|
}
|
|
data, _ := json.Marshal(nordData)
|
|
err = s.SettingService.SetNord(string(data))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(data), nil
|
|
}
|
|
|
|
func (s *NordService) GetNordData() (string, error) {
|
|
return s.SettingService.GetNord()
|
|
}
|
|
|
|
func (s *NordService) DelNordData() error {
|
|
return s.SettingService.SetNord("")
|
|
}
|