chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -0,0 +1,166 @@
// Package catalog 提供从官方 Telegram 拉取并固化的「目录/参考数据」种子:
// 国家区号(help.getCountriesList)、时区(help.getTimezonesList)、emoji 分组
// (messages.getEmojiGroups)、emoji 关键词(messages.getEmojiKeywords)。
//
// 数据由 cmd/catalogfetch 经官方 API 拉取后写入本目录的 JSON,
// 经 go:embed 进二进制。改了 JSON 即改了内容 hash,客户端发旧 hash 会被驱动重取。
// 各 accessor 在数据为空(未 seed)时返回空集,由调用方决定是否回退到内置最小数据。
package catalog
import (
_ "embed"
"encoding/json"
"hash/fnv"
"sync"
"telesrv/internal/domain"
)
//go:embed countries.json
var countriesJSON []byte
//go:embed timezones.json
var timezonesJSON []byte
//go:embed emoji_groups.json
var emojiGroupsJSON []byte
//go:embed emoji_keywords_en.json
var emojiKeywordsENJSON []byte
type countriesFile struct {
Countries []countryJSON `json:"countries"`
}
type countryJSON struct {
ISO2 string `json:"iso2"`
DefaultName string `json:"default_name"`
Name string `json:"name"`
Hidden bool `json:"hidden"`
CountryCodes []countryCodeJSON `json:"country_codes"`
}
type countryCodeJSON struct {
CountryCode string `json:"country_code"`
Prefixes []string `json:"prefixes"`
Patterns []string `json:"patterns"`
}
// Timezone 是 help.getTimezonesList 的一条时区。
type Timezone struct {
ID string `json:"id"`
Name string `json:"name"`
UTCOffset int `json:"utc_offset"`
}
type timezonesFile struct {
Timezones []Timezone `json:"timezones"`
}
// EmojiGroup 是 messages.getEmojiGroups 的一个分类。IconEmojiID 指向一个 custom-emoji
// 文档(本地若无对应文档,客户端分类图标回退为占位,不影响分组本身)。
type EmojiGroup struct {
Title string `json:"title"`
IconEmojiID int64 `json:"icon_emoji_id"`
Emoticons []string `json:"emoticons"`
}
type emojiGroupsFile struct {
Groups []EmojiGroup `json:"groups"`
}
// EmojiKeyword 是 emoji 关键词词典的一条:一个关键词 → 一组 emoji。
type EmojiKeyword struct {
Keyword string `json:"keyword"`
Emoticons []string `json:"emoticons"`
}
// EmojiKeywordSet 是某语言的整份 emoji 关键词词典。
type EmojiKeywordSet struct {
LangCode string `json:"lang_code"`
Version int `json:"version"`
Keywords []EmojiKeyword `json:"keywords"`
}
var (
once sync.Once
countries domain.CountriesList
timezones []Timezone
timezonesHash int
emojiGroups []EmojiGroup
emojiGroupsHash int
emojiKeywordSets map[string]EmojiKeywordSet
)
func load() {
once.Do(func() {
var cf countriesFile
_ = json.Unmarshal(countriesJSON, &cf)
list := make([]domain.Country, 0, len(cf.Countries))
for _, c := range cf.Countries {
codes := make([]domain.CountryCode, 0, len(c.CountryCodes))
for _, cc := range c.CountryCodes {
codes = append(codes, domain.CountryCode{
CountryCode: cc.CountryCode,
Prefixes: cc.Prefixes,
Patterns: cc.Patterns,
})
}
list = append(list, domain.Country{
ISO2: c.ISO2,
DefaultName: c.DefaultName,
Name: c.Name,
Hidden: c.Hidden,
CountryCodes: codes,
})
}
countries = domain.CountriesList{Hash: hashBytes(countriesJSON), Countries: list}
var tf timezonesFile
_ = json.Unmarshal(timezonesJSON, &tf)
timezones = tf.Timezones
timezonesHash = hashBytes(timezonesJSON)
var ef emojiGroupsFile
_ = json.Unmarshal(emojiGroupsJSON, &ef)
emojiGroups = ef.Groups
emojiGroupsHash = hashBytes(emojiGroupsJSON)
emojiKeywordSets = map[string]EmojiKeywordSet{}
for _, raw := range [][]byte{emojiKeywordsENJSON} {
var ks EmojiKeywordSet
if err := json.Unmarshal(raw, &ks); err == nil && ks.LangCode != "" && len(ks.Keywords) > 0 {
emojiKeywordSets[ks.LangCode] = ks
}
}
})
}
// hashBytes 把内容映射为一个稳定的正整数 hash(FNV-1a 32),供 NotModified 协商。
// 内容变了 hash 必变,客户端发旧 hash 即不命中→重取。
func hashBytes(b []byte) int {
h := fnv.New32a()
_, _ = h.Write(b)
return int(h.Sum32() & 0x7fffffff)
}
// Countries 返回固化的国家区号目录(空表示未 seed,调用方应回退内置最小集)。
func Countries() domain.CountriesList {
load()
return countries
}
// Timezones 返回时区目录与其内容 hash(空表示未 seed)。
func Timezones() ([]Timezone, int) {
load()
return timezones, timezonesHash
}
// EmojiGroups 返回 emoji 分类目录与其内容 hash(空表示未 seed)。
func EmojiGroups() ([]EmojiGroup, int) {
load()
return emojiGroups, emojiGroupsHash
}
// EmojiKeywords 返回指定语言的 emoji 关键词词典;ok=false 表示该语言未 seed。
func EmojiKeywords(lang string) (EmojiKeywordSet, bool) {
load()
ks, ok := emojiKeywordSets[lang]
return ks, ok
}

View file

@ -0,0 +1,49 @@
package catalog
import "testing"
// TestSeededCatalogsLoaded 验证 go:embed 的官方目录数据被正确解析、非空。
// 内容由 cmd/catalogfetch 拉取;数字是下界,重拉变多不会让测试失败。
func TestSeededCatalogsLoaded(t *testing.T) {
cs := Countries()
if len(cs.Countries) < 200 {
t.Fatalf("countries = %d, want >= 200 (official ~235)", len(cs.Countries))
}
if cs.Hash == 0 {
t.Fatalf("countries hash = 0, want stable nonzero")
}
var cnHasPattern bool
for _, c := range cs.Countries {
if c.ISO2 == "CN" {
for _, cc := range c.CountryCodes {
if cc.CountryCode == "86" && len(cc.Patterns) > 0 {
cnHasPattern = true
}
}
}
}
if !cnHasPattern {
t.Fatalf("CN(+86) missing phone pattern")
}
tzs, tzHash := Timezones()
if len(tzs) < 100 {
t.Fatalf("timezones = %d, want >= 100 (official ~419)", len(tzs))
}
if tzHash == 0 {
t.Fatalf("timezones hash = 0")
}
groups, gHash := EmojiGroups()
if len(groups) == 0 || gHash == 0 {
t.Fatalf("emoji groups = %d hash=%d, want non-empty", len(groups), gHash)
}
if len(groups[0].Emoticons) == 0 {
t.Fatalf("emoji group %q has no emoticons", groups[0].Title)
}
set, ok := EmojiKeywords("en")
if !ok || set.Version == 0 || len(set.Keywords) < 1000 {
t.Fatalf("en emoji keywords ok=%v version=%d count=%d, want seeded (official ~4286)", ok, set.Version, len(set.Keywords))
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,209 @@
{
"groups": [
{
"title": "Love",
"icon_emoji_id": 5355088357070218139,
"emoticons": [
"❤",
"😍",
"🥰",
"😘",
"☺",
"🤗",
"😚",
"😙",
"💋",
"🍑",
"👄",
"🫦",
"😻",
"❤‍🔥",
"💗",
"💞",
"💕",
"💖",
"🧡",
"💛",
"💚",
"💙",
"💜",
"🖤",
"🤍",
"🤎",
"💓",
"💘",
"💝",
"💟",
"👩‍❤‍👨",
"👩‍❤‍👩",
"💑",
"👨‍❤‍👨",
"👩‍❤‍💋‍👨",
"👩‍❤‍💋‍👩",
"💏",
"👨‍❤‍💋‍👨"
]
},
{
"title": "Approval",
"icon_emoji_id": 5357465415310124060,
"emoticons": [
"👍",
"👏",
"👌",
"💪",
"😌",
"✌",
"🤝",
"✔",
"🆗",
"✅"
]
},
{
"title": "Disapproval",
"icon_emoji_id": 5357216259962315463,
"emoticons": [
"👎",
"😒",
"🤮",
"🤢",
"🤦‍♂",
"🤦‍♀",
"🙅‍♀",
"🙅‍♂"
]
},
{
"title": "Cheers",
"icon_emoji_id": 5357257143756005264,
"emoticons": [
"🎉",
"🥳",
"🤩",
"🎊",
"🥂",
"🍾",
"🌟",
"🍻",
"🎂",
"💃",
"🎁"
]
},
{
"title": "Laughter",
"icon_emoji_id": 5354785574760753200,
"emoticons": [
"😄",
"😁",
"😆",
"😅",
"😂",
"🤣",
"😀",
"🤭",
"😃",
"😬",
"😈"
]
},
{
"title": "Astonishment",
"icon_emoji_id": 5354978715145085779,
"emoticons": [
"😨",
"😦",
"😱",
"😯",
"😧",
"😮",
"😲",
"🤯",
"😵",
"😳",
"🫣",
"😰"
]
},
{
"title": "Sadness",
"icon_emoji_id": 5355181227148059369,
"emoticons": [
"😔",
"😪",
"😭",
"😢",
"😣",
"😞",
"🥺",
"💔",
"☹",
"😕",
"🙁",
"🥲",
"😒",
"😥",
"😫",
"😮‍💨",
"🫤",
"😟"
]
},
{
"title": "Anger",
"icon_emoji_id": 5355125405458113381,
"emoticons": [
"😡",
"🤬",
"👿",
"😠",
"😤",
"🖕",
"💢"
]
},
{
"title": "Neutral",
"icon_emoji_id": 5357252827313874903,
"emoticons": [
"😐",
"😑",
"😕",
"😶",
"🙃",
"🙂",
"🫠",
"🤐",
"🥶"
]
},
{
"title": "Doubt",
"icon_emoji_id": 5354848362887653592,
"emoticons": [
"🤔",
"🤨",
"🧐",
"🙄",
"🥸",
"😵‍💫",
"❓",
"🤷‍♀",
"🤷‍♂"
]
},
{
"title": "Silly",
"icon_emoji_id": 5354832952544993830,
"emoticons": [
"🤪",
"😜",
"😝",
"😛",
"🤡",
"🥴"
]
}
]
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff