package server import ( "be-vpn/internal/dto" "be-vpn/internal/model" "fmt" "github.com/gin-gonic/gin" "log" "net/http" "sort" "sync" "time" ) var nodes = make([]*model.Node, 0) var locker = sync.RWMutex{} func Register(c *gin.Context) { locker.Lock() defer locker.Unlock() var request dto.RegisterRequest if err := c.ShouldBindJSON(&request); err != nil { dto.BadRequest(c, err) return } for _, node := range nodes { if node.Ip == request.Ip { node.Ip = request.Ip node.Secret = request.Secret node.CountryCode = request.CountryCode node.CountryName = request.CountryName node.City = request.City node.Vip = request.Vip node.LastUpdateTime = time.Now() c.JSON(http.StatusOK, dto.RegisterResponse{ Response: dto.NewOkResponse(), Data: dto.RegisterResult{ Success: true, }, }) return } } node := &model.Node{ Ip: request.Ip, Secret: request.Secret, LastUpdateTime: time.Now(), } nodes = append(nodes, node) log.Printf("update nodes: %+v", nodes) } func Group(c *gin.Context) { group(c, "v.starttransfernow.com", func(node *model.Node) bool { return true }) } func V2Group(c *gin.Context) { vip := c.Query("vip") group(c, "v.sweeterlife.net", func(node *model.Node) bool { if vip == "" { return true } if vip == "true" && node.Vip { return true } if vip == "false" && !node.Vip { return true } return false }) } func group(c *gin.Context, host string, predicate func(*model.Node) bool) { locker.RLock() defer locker.RUnlock() nodes := healthNodes() sort.SliceStable(nodes, func(i, j int) bool { return nodes[i].CountryCode > nodes[j].CountryCode }) countryLabelSeqs := make(map[string]int) dtoNodes := make([]*dto.Node, 0) for _, node := range nodes { if predicate(node) { seq, ok := countryLabelSeqs[node.CountryCode] if !ok { countryLabelSeqs[node.CountryCode] = 0 } else { countryLabelSeqs[node.CountryCode] = seq + 1 } dtoNodes = append(dtoNodes, convert2DtoNode(node, host)) } } continentMaps := make(map[string]bool) for _, node := range dtoNodes { continentMaps[node.Continent] = true } continents := make([]string, 0) for continent := range continentMaps { continents = append(continents, continent) } sort.SliceStable(continents, func(i, j int) bool { return continents[i] <= continents[j] }) groupDtos := make([]*dto.Group, 0) for _, continent := range continents { groupDto := &dto.Group{ Continent: continent, Nodes: make([]*dto.Node, 0), } for _, dtoNode := range dtoNodes { if dtoNode.Continent == continent { groupDto.Nodes = append(groupDto.Nodes, dtoNode) } } groupDtos = append(groupDtos, groupDto) } c.JSON(http.StatusOK, dto.GroupResponse{ Response: dto.NewOkResponse(), Data: groupDtos, }) } func Secret(c *gin.Context) { locker.RLock() defer locker.RUnlock() var request dto.DetailRequest if err := c.ShouldBindQuery(&request); err != nil { dto.BadRequest(c, err) return } for _, node := range nodes { if node.Ip == request.Ip { c.Header("Content-Disposition", "attachment; filename=client.ovpn") c.Data(http.StatusOK, "plain/text", []byte(node.Secret)) return } } c.JSON(http.StatusNotFound, gin.H{ "message": "not found ip", }) } func Health(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "up"}) } func healthNodes() []*model.Node { healthNodes := make([]*model.Node, 0) for _, node := range nodes { if node.LastUpdateTime.Add(10 * time.Second).After(time.Now()) { healthNodes = append(healthNodes, node) } } return healthNodes } func convert2DtoNode(node *model.Node, host string) *dto.Node { if node == nil { return nil } icons := map[string]string{ "AE": "http://v.starttransfernow.com/static/AE.png", "AU": "http://v.starttransfernow.com/static/AU.png", "BH": "http://v.starttransfernow.com/static/BH.png", "BR": "http://v.starttransfernow.com/static/BR.png", "CA": "http://v.starttransfernow.com/static/CA.png", "CH": "http://v.starttransfernow.com/static/CH.png", "DE": "http://v.starttransfernow.com/static/DE.png", "ES": "http://v.starttransfernow.com/static/ES.png", "FR": "http://v.starttransfernow.com/static/FR.png", "GB": "http://v.starttransfernow.com/static/GB.png", "HK": "http://v.starttransfernow.com/static/HK.png", "ID": "http://v.starttransfernow.com/static/ID.png", "IL": "http://v.starttransfernow.com/static/IL.png", "IN": "http://v.starttransfernow.com/static/IN.png", "IT": "http://v.starttransfernow.com/static/IT.png", "JP": "http://v.starttransfernow.com/static/JP.png", "KR": "http://v.starttransfernow.com/static/KR.png", "SE": "http://v.starttransfernow.com/static/SE.png", "SG": "http://v.starttransfernow.com/static/SG.png", "US": "http://v.starttransfernow.com/static/US.png", "ZA": "http://v.starttransfernow.com/static/ZA.png", "NL": "http://v.starttransfernow.com/static/NL.png", } countryLabels := map[string]string{ "AE": "United Arab Emirates", "AU": "Australia", "BH": "Bahrain", "BR": "Brazil", "CA": "Canada", "CH": "Switzerland", "DE": "Germany", "ES": "Spain", "FR": "France", "GB": "United Kingdom", "HK": "Hong Kong", "ID": "Indonesia", "IL": "Israel", "IN": "India", "IT": "Italy", "JP": "Japan", "KR": "South Korea", "SE": "Sweden", "SG": "Singapore", "US": "United States", "ZA": "South Africa", } countryContinents := map[string]string{ "AE": "asia", "AU": "oce", "BH": "asia", "BR": "southam", "CA": "northam", "CH": "eu", "DE": "eu", "ES": "eu", "FR": "eu", "GB": "eu", "HK": "asia", "ID": "asia", "IL": "asia", "IN": "asia", "IT": "eu", "JP": "asia", "KR": "asia", "SE": "eu", "SG": "asia", "US": "northam", "ZA": "southam", "NL": "eu", } countryLabel := fmt.Sprintf("%s", countryLabels[node.CountryCode]) return &dto.Node{ Ip: node.Ip, CountryCode: node.CountryCode, CountryName: node.CountryName, City: node.City, CountryLabel: countryLabel, Icon: icons[node.CountryCode], SecretUrl: fmt.Sprintf("http://%s/secret?ip=%s", host, node.Ip), Continent: countryContinents[node.CountryCode], Vip: node.Vip, } }