Files
Prism/internal/country/detect.go
T
rose_cat707 fc3a66dc83
CI / docker (push) Has been cancelled
CI / test (push) Has been cancelled
feat: Prism HTTP/SOCKS5 代理网关及管理台
Go 控制面(SQLite + REST API)嵌入 Mihomo 数据面,Vue 3 多语言 Web 管理台。
含订阅/节点/规则/出站/监控/日志、OpenAPI、API Key、Gitea Actions CI(runs-on: ubuntu-latest)
与开源文档;镜像发布至 git.rc707blog.top Container Registry。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 13:41:56 +08:00

167 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package country
import (
"sort"
"strings"
)
type entry struct {
Code string
Name string
Keywords []string
}
var (
sortedKeywords []keywordMatch
nameByCode map[string]string
)
type keywordMatch struct {
keyword string
code string
}
func init() {
nameByCode = make(map[string]string, len(catalog))
for _, e := range catalog {
nameByCode[e.Code] = e.Name
for _, kw := range e.Keywords {
sortedKeywords = append(sortedKeywords, keywordMatch{keyword: strings.ToLower(kw), code: e.Code})
}
}
sort.Slice(sortedKeywords, func(i, j int) bool {
return len(sortedKeywords[i].keyword) > len(sortedKeywords[j].keyword)
})
}
// Detect returns ISO-like country code from node name, or empty string.
func Detect(name string) string {
lower := strings.ToLower(strings.TrimSpace(name))
if lower == "" {
return ""
}
normalized := strings.NewReplacer(
"_", " ", "-", " ", "|", " ", "/", " ", "[", " ", "]", " ",
"(", " ", ")", " ", "【", " ", "】", " ", "", " ", "", " ",
).Replace(lower)
padded := " " + normalized + " "
for _, m := range sortedKeywords {
kw := m.keyword
if len(kw) <= 2 {
continue
}
if keywordMatches(padded, kw) {
return m.code
}
}
for _, tok := range strings.Fields(normalized) {
tok = strings.TrimSpace(tok)
if tok == "" {
continue
}
if len(tok) == 2 {
if _, ok := nameByCode[strings.ToUpper(tok)]; ok {
return strings.ToUpper(tok)
}
continue
}
if len(tok) > 2 {
prefix := strings.ToUpper(tok[:2])
if _, ok := nameByCode[prefix]; !ok {
continue
}
suffix := tok[2:]
if suffix == "" {
continue
}
allDigits := true
for _, r := range suffix {
if r < '0' || r > '9' {
allDigits = false
break
}
}
if allDigits {
return prefix
}
}
}
return ""
}
// Name returns Chinese display name for a country code.
func Name(code string) string {
if n, ok := nameByCode[strings.ToUpper(code)]; ok {
return n
}
return code
}
// GroupRuntime returns Mihomo proxy-group name for a country pool.
func GroupRuntime(code string) string {
return "country_" + strings.ToUpper(strings.TrimSpace(code))
}
// TargetAlias returns rule target alias stored in DB, e.g. country:HK.
func TargetAlias(code string) string {
return "country:" + strings.ToUpper(strings.TrimSpace(code))
}
// ParseTargetAlias parses country:XX to code, ok false if not a country target.
func ParseTargetAlias(target string) (string, bool) {
if !strings.HasPrefix(strings.ToLower(target), "country:") {
return "", false
}
code := strings.TrimSpace(target[len("country:"):])
if code == "" {
return "", false
}
return strings.ToUpper(code), true
}
// All returns catalog entries for UI.
func All() []struct{ Code, Name string } {
out := make([]struct{ Code, Name string }, 0, len(catalog))
for _, e := range catalog {
out = append(out, struct{ Code, Name string }{Code: e.Code, Name: e.Name})
}
return out
}
func keywordMatches(text, kw string) bool {
if !strings.Contains(text, kw) {
return false
}
if containsHan(kw) {
return true
}
start := 0
for {
idx := strings.Index(text[start:], kw)
if idx < 0 {
return false
}
pos := start + idx
before := pos == 0 || !isASCIIAlphaNum(text[pos-1])
afterEnd := pos + len(kw)
after := afterEnd >= len(text) || !isASCIIAlphaNum(text[afterEnd])
if before && after {
return true
}
start = pos + 1
}
}
func isASCIIAlphaNum(b byte) bool {
return (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9')
}
func containsHan(s string) bool {
for _, r := range s {
if r >= 0x4e00 && r <= 0x9fff {
return true
}
}
return false
}