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

@ -1,15 +1,13 @@
package tdesktop
import (
"crypto/sha256"
"encoding/binary"
"time"
"github.com/gotd/td/tg"
"telesrv/internal/seed/catalog"
)
const (
appConfigHash = 4
appConfigHash = 12 // app config 内容变更时必须递增,否则缓存端只会收到 notModified。
countriesListHash = 1
timezonesListHash = 1
)
@ -21,76 +19,149 @@ func AppConfig(hash int) tg.HelpAppConfigClass {
}
return &tg.HelpAppConfig{
Hash: appConfigHash,
Config: readMarkAppConfig(),
Config: readMarkAppConfig(""),
}
}
func readMarkAppConfig() *tg.JSONObject {
return &tg.JSONObject{Value: []tg.JSONObjectValue{
func readMarkAppConfig(mapboxToken string) *tg.JSONObject {
values := []tg.JSONObjectValue{
{Key: "chat_read_mark_size_threshold", Value: &tg.JSONNumber{Value: 50}},
{Key: "chat_read_mark_expire_period", Value: &tg.JSONNumber{Value: 604800}},
{Key: "pm_read_date_expire_period", Value: &tg.JSONNumber{Value: 604800}},
{Key: "quote_length_max", Value: &tg.JSONNumber{Value: 1024}},
{Key: "telegram_antispam_group_size_min", Value: &tg.JSONNumber{Value: 200}},
{Key: "telegram_antispam_user_id", Value: &tg.JSONString{Value: "5434988373"}},
}}
// premium_purchase_blocked=false:客户端把 star gift「Send a Gift」入口与
// premiumCanBuy()=!premium_purchase_blocked 耦合,置 true 会同时隐藏送礼入口
// (详见 app/help/service.go 主配置注释)。这里是无 HelpService 时的最小回退。
{Key: "premium_purchase_blocked", Value: &tg.JSONBool{Value: false}},
// stargifts_blocked=false:DrKLO 缺省 stargiftsBlocked=true 会隐藏 star gift 送礼网格。
{Key: "stargifts_blocked", Value: &tg.JSONBool{Value: false}},
{Key: "reactions_user_max_premium", Value: &tg.JSONNumber{Value: 3}},
// dialog_filters_enabled=true:TDesktop 据此(或已有文件夹)才显示 Settings→Folders 入口。
{Key: "dialog_filters_enabled", Value: &tg.JSONBool{Value: true}},
{Key: "stories_stealth_future_period", Value: &tg.JSONNumber{Value: 1500}},
{Key: "stories_stealth_past_period", Value: &tg.JSONNumber{Value: 300}},
{Key: "stories_stealth_cooldown_period", Value: &tg.JSONNumber{Value: 10800}},
// upload_markup_video=true 即官方默认(emoji/sticker 头像由客户端本地渲染 mp4 后随
// markup 一起上传)。显式下发是为了把曾收到过 false 的客户端持久化配置洗回默认——
// 客户端对缺失的 key 会保留本地旧值,仅删除 key 无法恢复。
{Key: "upload_markup_video", Value: &tg.JSONBool{Value: true}},
// 单 emoji 消息转 InputMediaDice 的白名单;须与 rpc 层 dice 取值表同步。
{Key: "emojies_send_dice", Value: &tg.JSONArray{Value: []tg.JSONValueClass{
&tg.JSONString{Value: "\U0001F3B2"}, // 🎲
&tg.JSONString{Value: "\U0001F3AF"}, // 🎯
&tg.JSONString{Value: "\U0001F3C0"}, // 🏀
&tg.JSONString{Value: "⚽"},
&tg.JSONString{Value: "⚽️"},
&tg.JSONString{Value: "\U0001F3B3"}, // 🎳
&tg.JSONString{Value: "\U0001F3B0"}, // 🎰
}}},
}
if mapboxToken != "" {
// TDesktop 位置选点器(WebView+Mapbox GL)与 business 位置设置的地图 token;
// 缺失时选点器地图空白(瓦片直连 api.mapbox.com,不经服务端)。
values = append(values, tg.JSONObjectValue{Key: "tdesktop_config_map", Value: &tg.JSONObject{Value: []tg.JSONObjectValue{
{Key: "maps", Value: &tg.JSONString{Value: mapboxToken}},
{Key: "geo", Value: &tg.JSONString{Value: mapboxToken}},
{Key: "bmaps", Value: &tg.JSONString{Value: mapboxToken}},
{Key: "bgeo", Value: &tg.JSONString{Value: mapboxToken}},
}}})
}
return &tg.JSONObject{Value: values}
}
// TimezonesList returns a small non-empty timezone set for TDesktop business settings preloading.
// fallbackTimezones 是 catalog 未 seed 时的内置最小时区集。
var fallbackTimezones = []tg.Timezone{
{ID: "Etc/UTC", Name: "UTC", UtcOffset: 0},
{ID: "America/New_York", Name: "Eastern Time", UtcOffset: -5 * 60 * 60},
{ID: "America/Chicago", Name: "Central Time", UtcOffset: -6 * 60 * 60},
{ID: "America/Denver", Name: "Mountain Time", UtcOffset: -7 * 60 * 60},
{ID: "America/Los_Angeles", Name: "Pacific Time", UtcOffset: -8 * 60 * 60},
{ID: "Asia/Shanghai", Name: "China Standard Time", UtcOffset: 8 * 60 * 60},
}
// TimezonesList 返回时区目录:优先用 catalog 固化的官方全量(~419 个),未 seed 时回退内置最小集。
func TimezonesList(hash int) tg.HelpTimezonesListClass {
if hash == timezonesListHash {
seeded, h := catalog.Timezones()
src := seeded
if len(src) == 0 {
src, h = nil, timezonesListHash
}
if hash == h {
return &tg.HelpTimezonesListNotModified{}
}
return &tg.HelpTimezonesList{
Hash: timezonesListHash,
Timezones: []tg.Timezone{
{ID: "Etc/UTC", Name: "UTC", UtcOffset: 0},
{ID: "America/New_York", Name: "Eastern Time", UtcOffset: -5 * 60 * 60},
{ID: "America/Chicago", Name: "Central Time", UtcOffset: -6 * 60 * 60},
{ID: "America/Denver", Name: "Mountain Time", UtcOffset: -7 * 60 * 60},
{ID: "America/Los_Angeles", Name: "Pacific Time", UtcOffset: -8 * 60 * 60},
{ID: "Asia/Shanghai", Name: "China Standard Time", UtcOffset: 8 * 60 * 60},
},
items := fallbackTimezones
if len(src) > 0 {
items = make([]tg.Timezone, 0, len(src))
for _, t := range src {
items = append(items, tg.Timezone{ID: t.ID, Name: t.Name, UtcOffset: t.UTCOffset})
}
}
return &tg.HelpTimezonesList{Hash: h, Timezones: items}
}
// SuggestedDialogFilters 返回新建对话文件夹时的「推荐文件夹」模板(官方固定语义:
// Unread/Personal/Unmuted/Groups/Channels/Bots)。客户端据此一键创建对应规则的文件夹;
// 模板 id 仅占位,用户采纳时客户端会以新 id 调 updateDialogFilter 真正创建。
func SuggestedDialogFilters() []tg.DialogFilterSuggested {
mk := func(id int, title, desc string, set func(*tg.DialogFilter)) tg.DialogFilterSuggested {
df := &tg.DialogFilter{
ID: id,
Title: tg.TextWithEntities{Text: title, Entities: []tg.MessageEntityClass{}},
PinnedPeers: []tg.InputPeerClass{},
IncludePeers: []tg.InputPeerClass{},
ExcludePeers: []tg.InputPeerClass{},
}
set(df)
return tg.DialogFilterSuggested{Filter: df, Description: desc}
}
allTypes := func(d *tg.DialogFilter) {
d.Contacts, d.NonContacts, d.Groups, d.Broadcasts, d.Bots = true, true, true, true, true
}
return []tg.DialogFilterSuggested{
mk(2, "Unread", "New messages", func(d *tg.DialogFilter) { allTypes(d); d.ExcludeRead = true }),
mk(3, "Personal", "Personal chats", func(d *tg.DialogFilter) { d.Contacts, d.NonContacts = true, true }),
mk(4, "Unmuted", "Unmuted chats", func(d *tg.DialogFilter) { allTypes(d); d.ExcludeMuted = true }),
mk(5, "Groups", "Group chats", func(d *tg.DialogFilter) { d.Groups = true }),
mk(6, "Channels", "Channels only", func(d *tg.DialogFilter) { d.Broadcasts = true }),
mk(7, "Bots", "Bot chats", func(d *tg.DialogFilter) { d.Bots = true }),
}
}
// CountriesList returns the fallback login country list used when HelpService is absent.
// CountriesList 返回登录页国家区号目录:优先用 catalog 固化的官方全量(~235 国),未 seed
// 时回退内置最小集(US/CN)。生产路径通常经 HelpService.GetCountries → defaultCountries
// 走 catalog;此函数是 HelpService 缺省时的兜底。
func CountriesList(hash int) tg.HelpCountriesListClass {
if hash == countriesListHash {
list := catalog.Countries()
if len(list.Countries) == 0 {
if hash == countriesListHash {
return &tg.HelpCountriesListNotModified{}
}
return &tg.HelpCountriesList{
Hash: countriesListHash,
Countries: []tg.HelpCountry{
{ISO2: "US", DefaultName: "United States", CountryCodes: []tg.HelpCountryCode{{CountryCode: "1", Prefixes: []string{""}, Patterns: []string{"XXX XXX XXXX"}}}},
{ISO2: "CN", DefaultName: "China", CountryCodes: []tg.HelpCountryCode{{CountryCode: "86", Prefixes: []string{""}, Patterns: []string{"XXX XXXX XXXX"}}}},
},
}
}
if hash == list.Hash {
return &tg.HelpCountriesListNotModified{}
}
return &tg.HelpCountriesList{
Hash: countriesListHash,
Countries: []tg.HelpCountry{
{
ISO2: "US",
DefaultName: "United States",
CountryCodes: []tg.HelpCountryCode{
{CountryCode: "1", Prefixes: []string{"1"}},
},
},
{
ISO2: "CN",
DefaultName: "China",
CountryCodes: []tg.HelpCountryCode{
{CountryCode: "86", Prefixes: []string{"86"}},
},
},
},
}
}
// LoginToken returns a short-lived placeholder token for TDesktop's QR-login
// screen. First phase only supports phone login, but TDesktop expects this RPC
// to produce an auth.loginToken while the QR screen is visible.
func LoginToken(now time.Time, authKeyID [8]byte, sessionID int64) *tg.AuthLoginToken {
var seed [len(authKeyID) + 8 + 8]byte
copy(seed[:len(authKeyID)], authKeyID[:])
binary.LittleEndian.PutUint64(seed[len(authKeyID):], uint64(sessionID))
binary.LittleEndian.PutUint64(seed[len(authKeyID)+8:], uint64(now.UnixNano()))
token := sha256.Sum256(seed[:])
return &tg.AuthLoginToken{
Expires: int(now.Add(30 * time.Second).Unix()),
Token: token[:],
out := &tg.HelpCountriesList{Hash: list.Hash, Countries: make([]tg.HelpCountry, 0, len(list.Countries))}
for _, c := range list.Countries {
item := tg.HelpCountry{
Hidden: c.Hidden,
ISO2: c.ISO2,
DefaultName: c.DefaultName,
Name: c.Name,
CountryCodes: make([]tg.HelpCountryCode, 0, len(c.CountryCodes)),
}
for _, cc := range c.CountryCodes {
item.CountryCodes = append(item.CountryCodes, tg.HelpCountryCode{CountryCode: cc.CountryCode, Prefixes: cc.Prefixes, Patterns: cc.Patterns})
}
out.Countries = append(out.Countries, item)
}
return out
}