Release 3.9.0+1: Remnawave API configs, Remnawave-style UI with flags, Windows build.
EOF Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
// Package geoflag maps server/profile names to ISO 3166-1 alpha-2 and emoji flags.
|
||||
package geoflag
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
reBracketCC = regexp.MustCompile(`(?i)[\[(]([a-z]{2})[\])]`)
|
||||
rePrefixCC = regexp.MustCompile(`(?i)^([a-z]{2})(?:\s*[-_|/]\s*|\s+)`)
|
||||
reTokenCC = regexp.MustCompile(`(?i)(?:^|[^a-z])([a-z]{2})(?:\s*[-_|]|$)`)
|
||||
reEmojiFlag = regexp.MustCompile(`[\x{1F1E6}-\x{1F1FF}]{2}`)
|
||||
)
|
||||
|
||||
// FromText returns ISO2 (upper) and emoji flag, or empty if unknown.
|
||||
func FromText(name string) (iso2, emoji string) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return "", ""
|
||||
}
|
||||
if m := reEmojiFlag.FindString(name); m != "" {
|
||||
if cc := emojiToISO(m); cc != "" {
|
||||
return cc, m
|
||||
}
|
||||
}
|
||||
lower := strings.ToLower(name)
|
||||
for _, pair := range countryNames {
|
||||
if strings.Contains(lower, pair.name) {
|
||||
return pair.code, ISOToEmoji(pair.code)
|
||||
}
|
||||
}
|
||||
if m := reBracketCC.FindStringSubmatch(name); len(m) == 2 {
|
||||
cc := strings.ToUpper(m[1])
|
||||
if Known(cc) {
|
||||
return cc, ISOToEmoji(cc)
|
||||
}
|
||||
}
|
||||
if m := rePrefixCC.FindStringSubmatch(name); len(m) == 2 {
|
||||
cc := strings.ToUpper(m[1])
|
||||
if Known(cc) {
|
||||
return cc, ISOToEmoji(cc)
|
||||
}
|
||||
}
|
||||
if m := reTokenCC.FindStringSubmatch(name); len(m) == 2 {
|
||||
cc := strings.ToUpper(m[1])
|
||||
if Known(cc) {
|
||||
return cc, ISOToEmoji(cc)
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// Known reports whether cc is a mapped ISO2 code.
|
||||
func Known(cc string) bool {
|
||||
cc = strings.ToUpper(strings.TrimSpace(cc))
|
||||
_, ok := isoSet[cc]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ISOToEmoji converts "DE" → "🇩🇪". Invalid → "".
|
||||
func ISOToEmoji(cc string) string {
|
||||
cc = strings.ToUpper(strings.TrimSpace(cc))
|
||||
if len(cc) != 2 {
|
||||
return ""
|
||||
}
|
||||
a, b := rune(cc[0]), rune(cc[1])
|
||||
if a < 'A' || a > 'Z' || b < 'A' || b > 'Z' {
|
||||
return ""
|
||||
}
|
||||
return string(rune(0x1F1E6+a-'A')) + string(rune(0x1F1E6+b-'A'))
|
||||
}
|
||||
|
||||
func emojiToISO(flag string) string {
|
||||
rs := []rune(flag)
|
||||
if len(rs) != 2 {
|
||||
return ""
|
||||
}
|
||||
a, b := rs[0]-0x1F1E6, rs[1]-0x1F1E6
|
||||
if a < 0 || a > 25 || b < 0 || b > 25 {
|
||||
return ""
|
||||
}
|
||||
cc := string([]byte{byte('A' + a), byte('A' + b)})
|
||||
if !Known(cc) {
|
||||
// Still return regional indicator pair as ISO guess for display.
|
||||
if unicode.IsLetter(rune('A'+a)) {
|
||||
return cc
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return cc
|
||||
}
|
||||
|
||||
type nameCode struct {
|
||||
name string
|
||||
code string
|
||||
}
|
||||
|
||||
// Longer / more specific names first where needed.
|
||||
var countryNames = []nameCode{
|
||||
{"netherlands", "NL"}, {"deutschland", "DE"}, {"germany", "DE"},
|
||||
{"united kingdom", "GB"}, {"great britain", "GB"}, {"england", "GB"},
|
||||
{"united states", "US"}, {"america", "US"}, {"finland", "FI"},
|
||||
{"sweden", "SE"}, {"norway", "NO"}, {"denmark", "DK"}, {"poland", "PL"},
|
||||
{"france", "FR"}, {"spain", "ES"}, {"italy", "IT"}, {"portugal", "PT"},
|
||||
{"switzerland", "CH"}, {"austria", "AT"}, {"belgium", "BE"},
|
||||
{"czech", "CZ"}, {"slovakia", "SK"}, {"hungary", "HU"}, {"romania", "RO"},
|
||||
{"bulgaria", "BG"}, {"ukraine", "UA"}, {"russia", "RU"}, {"turkey", "TR"},
|
||||
{"israel", "IL"}, {"uae", "AE"}, {"emirates", "AE"}, {"dubai", "AE"},
|
||||
{"singapore", "SG"}, {"hong kong", "HK"}, {"japan", "JP"}, {"korea", "KR"},
|
||||
{"taiwan", "TW"}, {"india", "IN"}, {"canada", "CA"}, {"mexico", "MX"},
|
||||
{"brazil", "BR"}, {"argentina", "AR"}, {"australia", "AU"},
|
||||
{"new zealand", "NZ"}, {"latvia", "LV"}, {"lithuania", "LT"},
|
||||
{"estonia", "EE"}, {"moldova", "MD"}, {"georgia", "GE"}, {"armenia", "AM"},
|
||||
{"kazakhstan", "KZ"}, {"uzbekistan", "UZ"}, {"serbia", "RS"},
|
||||
{"croatia", "HR"}, {"slovenia", "SI"}, {"greece", "GR"}, {"cyprus", "CY"},
|
||||
{"ireland", "IE"}, {"iceland", "IS"}, {"luxembourg", "LU"},
|
||||
{"malaysia", "MY"}, {"thailand", "TH"}, {"vietnam", "VN"},
|
||||
{"indonesia", "ID"}, {"philippines", "PH"}, {"south africa", "ZA"},
|
||||
}
|
||||
|
||||
var isoSet = map[string]struct{}{
|
||||
"AD": {}, "AE": {}, "AF": {}, "AL": {}, "AM": {}, "AR": {}, "AT": {}, "AU": {},
|
||||
"AZ": {}, "BA": {}, "BD": {}, "BE": {}, "BG": {}, "BH": {}, "BR": {}, "BY": {},
|
||||
"CA": {}, "CH": {}, "CL": {}, "CN": {}, "CO": {}, "CY": {}, "CZ": {}, "DE": {},
|
||||
"DK": {}, "EE": {}, "EG": {}, "ES": {}, "FI": {}, "FR": {}, "GB": {}, "GE": {},
|
||||
"GR": {}, "HK": {}, "HR": {}, "HU": {}, "ID": {}, "IE": {}, "IL": {}, "IN": {},
|
||||
"IQ": {}, "IR": {}, "IS": {}, "IT": {}, "JP": {}, "KG": {}, "KR": {}, "KZ": {},
|
||||
"LT": {}, "LU": {}, "LV": {}, "MD": {}, "MK": {}, "MX": {}, "MY": {}, "NL": {},
|
||||
"NO": {}, "NZ": {}, "PH": {}, "PK": {}, "PL": {}, "PT": {}, "RO": {}, "RS": {},
|
||||
"RU": {}, "SA": {}, "SE": {}, "SG": {}, "SI": {}, "SK": {}, "TH": {}, "TJ": {},
|
||||
"TM": {}, "TR": {}, "TW": {}, "UA": {}, "US": {}, "UZ": {}, "VN": {}, "ZA": {},
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package geoflag
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFromText(t *testing.T) {
|
||||
cases := []struct {
|
||||
in, cc string
|
||||
}{
|
||||
{"DE-Frankfurt-1", "DE"},
|
||||
{"[NL] Amsterdam", "NL"},
|
||||
{"🇺🇸 US West", "US"},
|
||||
{"Germany Hetzner", "DE"},
|
||||
{"unknown-node", ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
cc, emoji := FromText(c.in)
|
||||
if cc != c.cc {
|
||||
t.Fatalf("%q: got cc %q want %q", c.in, cc, c.cc)
|
||||
}
|
||||
if c.cc != "" && emoji == "" {
|
||||
t.Fatalf("%q: empty emoji", c.in)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user