feat: GeoIP-based geo detection + configurable rename template
- Add MaxMind GeoLite2-City mmdb auto-download and IP-based geo lookup (covers all 249 countries globally, replacing limited 8-country regex) - Add RenameOptions with 5 toggleable fields (flag/alias/country/city/index) stored as rename_options_json on collections - Collection rename pipeline uses DetectGeoWithServer(name, server) which tries GeoIP lookup first, falls back to name-based regex matching - Chinese country/city names + emoji flag prefix - Country-city dedup: skip city when it equals country (e.g. 香港/香港) - URI fragment encoding: url.QueryEscape -> url.PathEscape (%20 not +) - Egress cache key excludes name field so renamed nodes still match cache - Frontend: 5 toggle buttons in collection edit form, default all on - Migrations: 0004 (source alias + collection rename_enabled), 0005 (collection rename_options_json)
This commit is contained in:
@@ -69,7 +69,7 @@ func ToProxyUri(proxy model.ProxyNode) string {
|
||||
url.PathEscape(getString(proxy, "uuid")),
|
||||
server, port,
|
||||
params.Encode(),
|
||||
url.QueryEscape(name))
|
||||
url.PathEscape(name))
|
||||
|
||||
case "hysteria2":
|
||||
params := url.Values{}
|
||||
@@ -89,7 +89,7 @@ func ToProxyUri(proxy model.ProxyNode) string {
|
||||
url.PathEscape(getString(proxy, "password")),
|
||||
server, port,
|
||||
params.Encode(),
|
||||
url.QueryEscape(name))
|
||||
url.PathEscape(name))
|
||||
|
||||
case "hysteria":
|
||||
params := url.Values{}
|
||||
@@ -118,7 +118,7 @@ func ToProxyUri(proxy model.ProxyNode) string {
|
||||
url.PathEscape(getString(proxy, "auth_str")),
|
||||
server, port,
|
||||
params.Encode(),
|
||||
url.QueryEscape(name))
|
||||
url.PathEscape(name))
|
||||
|
||||
case "anytls":
|
||||
params := url.Values{}
|
||||
@@ -135,7 +135,7 @@ func ToProxyUri(proxy model.ProxyNode) string {
|
||||
url.PathEscape(getString(proxy, "password")),
|
||||
server, port,
|
||||
params.Encode(),
|
||||
url.QueryEscape(name))
|
||||
url.PathEscape(name))
|
||||
|
||||
case "tuic":
|
||||
params := url.Values{}
|
||||
@@ -162,7 +162,7 @@ func ToProxyUri(proxy model.ProxyNode) string {
|
||||
url.PathEscape(getString(proxy, "password")),
|
||||
server, port,
|
||||
params.Encode(),
|
||||
url.QueryEscape(name))
|
||||
url.PathEscape(name))
|
||||
|
||||
case "trojan":
|
||||
params := url.Values{}
|
||||
@@ -176,7 +176,7 @@ func ToProxyUri(proxy model.ProxyNode) string {
|
||||
url.PathEscape(getString(proxy, "password")),
|
||||
server, port,
|
||||
params.Encode(),
|
||||
url.QueryEscape(name))
|
||||
url.PathEscape(name))
|
||||
|
||||
case "socks5":
|
||||
params := url.Values{}
|
||||
@@ -208,14 +208,14 @@ func ToProxyUri(proxy model.ProxyNode) string {
|
||||
scheme = "https"
|
||||
}
|
||||
return fmt.Sprintf("%s://%s%s:%d#%s",
|
||||
scheme, auth, server, port, url.QueryEscape(name))
|
||||
scheme, auth, server, port, url.PathEscape(name))
|
||||
|
||||
case "ss":
|
||||
userInfo := util.Base64Utf8(fmt.Sprintf("%s:%s@%s:%d",
|
||||
getString(proxy, "cipher"),
|
||||
getString(proxy, "password"),
|
||||
server, port))
|
||||
return fmt.Sprintf("ss://%s#%s", userInfo, url.QueryEscape(name))
|
||||
return fmt.Sprintf("ss://%s#%s", userInfo, url.PathEscape(name))
|
||||
|
||||
case "ssr":
|
||||
main := strings.Join([]string{
|
||||
@@ -257,7 +257,7 @@ func ToProxyUri(proxy model.ProxyNode) string {
|
||||
url.PathEscape(getString(proxy, "private-key")),
|
||||
server, port,
|
||||
params.Encode(),
|
||||
url.QueryEscape(name))
|
||||
url.PathEscape(name))
|
||||
|
||||
case "vmess":
|
||||
wsOpts, _ := proxy["ws-opts"].(map[string]any)
|
||||
|
||||
Reference in New Issue
Block a user