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

@ -4,6 +4,7 @@ import (
"context"
"github.com/gotd/td/tg"
"go.uber.org/zap"
"telesrv/internal/compat/tdesktop"
"telesrv/internal/domain"
@ -34,6 +35,34 @@ func (r *Router) onMessagesGetAvailableReactions(ctx context.Context, hash int)
return tgAvailableReactions(reactions, documentsByID(docs), catalogHash), nil
}
// onMessagesGetAvailableEffects 返回消息发送特效目录(全局静态,seed 进内存)。镜像
// getAvailableReactions:文档批量预加载后由 tgAvailableEffects 组装,hash 命中回 NotModified。
func (r *Router) onMessagesGetAvailableEffects(ctx context.Context, hash int) (tg.MessagesAvailableEffectsClass, error) {
empty := func() *tg.MessagesAvailableEffects {
return &tg.MessagesAvailableEffects{Hash: 0, Effects: []tg.AvailableEffect{}, Documents: []tg.DocumentClass{}}
}
if r.deps.Files == nil {
return empty(), nil
}
effects, catalogHash, err := r.deps.Files.AvailableEffects(ctx)
if err != nil {
return nil, internalErr()
}
if len(effects) == 0 {
return empty(), nil
}
// catalogHash 在 seed 时算好(全局静态),命中即零查库返回 NotModified——必须在
// GetDocuments(唯一打 PG 的点)之前。
if hash == catalogHash {
return &tg.MessagesAvailableEffectsNotModified{}, nil
}
docs, err := r.deps.Files.GetDocuments(ctx, effectDocumentIDs(effects))
if err != nil {
return nil, internalErr()
}
return tgAvailableEffects(effects, documentsByID(docs), catalogHash), nil
}
func (r *Router) onMessagesGetStickerSet(ctx context.Context, req *tg.MessagesGetStickerSetRequest) (tg.MessagesStickerSetClass, error) {
if r.deps.Files == nil {
return tdesktop.StickerSet(req), nil
@ -46,7 +75,34 @@ func (r *Router) onMessagesGetStickerSet(ctx context.Context, req *tg.MessagesGe
if err != nil {
return nil, internalErr()
}
// 观测:量化客户端反复请求哪些集(同集重试 vs 大量不同集)。未 seed 集走 stub,
// 由 ResolveStickerSet 的负缓存短路 PG;这里只记 ref 与命中情况。
if r.log != nil {
r.log.Debug("getStickerSet",
zap.String("ref_kind", string(ref.Kind)),
zap.String("short_name", ref.ShortName),
zap.Int64("set_id", ref.ID),
zap.String("system_key", ref.SystemKey),
zap.Bool("found", found),
)
}
if !found {
if fallbackSet, fallbackDocs, fallbackFound, fallbackErr := r.resolvePlaceholderStickerSet(ctx, ref); fallbackErr != nil {
return nil, internalErr()
} else if fallbackFound {
if r.log != nil {
r.log.Debug("getStickerSet placeholder fallback",
zap.String("short_name", ref.ShortName),
zap.Int64("fallback_set_id", fallbackSet.ID),
zap.String("fallback_short_name", fallbackSet.ShortName),
zap.Int("documents", len(fallbackDocs)),
)
}
if req.Hash != 0 && req.Hash == fallbackSet.Hash {
return &tg.MessagesStickerSetNotModified{}, nil
}
return tgMessagesStickerSet(fallbackSet, fallbackDocs), nil
}
// 未 seed 的系统集 / 未知短名:回退兼容 stub,避免破坏客户端。
return tdesktop.StickerSet(req), nil
}
@ -56,6 +112,69 @@ func (r *Router) onMessagesGetStickerSet(ctx context.Context, req *tg.MessagesGe
return tgMessagesStickerSet(set, docs), nil
}
func (r *Router) resolvePlaceholderStickerSet(ctx context.Context, ref domain.StickerSetRef) (domain.StickerSet, []domain.Document, bool, error) {
if ref.Kind != domain.StickerSetRefByShortName || !isClientPlaceholderStickerSet(ref.ShortName) {
return domain.StickerSet{}, nil, false, nil
}
for _, candidate := range placeholderStickerSetCandidates() {
set, docs, found, err := r.deps.Files.ResolveStickerSet(ctx, candidate)
if err != nil || !found {
if err != nil {
return domain.StickerSet{}, nil, false, err
}
continue
}
if len(docs) >= androidPlaceholderStickerMinDocuments {
return set, docs, true, nil
}
}
for _, kind := range []domain.StickerSetKind{domain.StickerSetKindSystem, domain.StickerSetKindEmoji, domain.StickerSetKindStickers} {
sets, err := r.deps.Files.ListStickerSets(ctx, kind)
if err != nil {
return domain.StickerSet{}, nil, false, err
}
for _, candidate := range sets {
if len(candidate.DocumentIDs) < androidPlaceholderStickerMinDocuments {
continue
}
set, docs, found, err := r.deps.Files.ResolveStickerSet(ctx, domain.StickerSetRef{
Kind: domain.StickerSetRefByID,
ID: candidate.ID,
AccessHash: candidate.AccessHash,
})
if err != nil {
return domain.StickerSet{}, nil, false, err
}
if found && len(docs) >= androidPlaceholderStickerMinDocuments {
return set, docs, true, nil
}
}
}
return domain.StickerSet{}, nil, false, nil
}
const androidPlaceholderStickerMinDocuments = 7
func isClientPlaceholderStickerSet(shortName string) bool {
switch shortName {
case "tg_placeholders_android", "tg_superplaceholders_android_2":
return true
default:
return false
}
}
func placeholderStickerSetCandidates() []domain.StickerSetRef {
return []domain.StickerSetRef{
{Kind: domain.StickerSetRefBySystem, SystemKey: "animated_emoji"},
{Kind: domain.StickerSetRefBySystem, SystemKey: "emoji_generic_animations"},
{Kind: domain.StickerSetRefBySystem, SystemKey: "animated_emoji_animations"},
{Kind: domain.StickerSetRefByShortName, ShortName: "AnimatedEmojies"},
{Kind: domain.StickerSetRefByShortName, ShortName: "EmojiGenericAnimations"},
{Kind: domain.StickerSetRefByShortName, ShortName: "EmojiAnimations"},
}
}
func (r *Router) onMessagesGetAllStickers(ctx context.Context, hash int64) (tg.MessagesAllStickersClass, error) {
return r.allStickersForKind(ctx, hash, domain.StickerSetKindStickers)
}
@ -64,14 +183,16 @@ func (r *Router) onMessagesGetEmojiStickers(ctx context.Context, hash int64) (tg
return r.allStickersForKind(ctx, hash, domain.StickerSetKindEmoji)
}
func (r *Router) onMessagesGetMaskStickers(ctx context.Context, hash int64) (tg.MessagesAllStickersClass, error) {
return r.allStickersForKind(ctx, hash, domain.StickerSetKindMasks)
}
func (r *Router) allStickersForKind(ctx context.Context, hash int64, kind domain.StickerSetKind) (tg.MessagesAllStickersClass, error) {
if r.deps.Files == nil {
return messagesAllStickersEmpty(hash), nil
}
sets, err := r.deps.Files.ListStickerSets(ctx, kind)
if err != nil {
return nil, internalErr()
}
// perf:从目录缓存读集(TTL 内 hash 命中不打 PG)。
sets := r.stickerCatalogSets(ctx, kind)
if len(sets) == 0 {
return messagesAllStickersEmpty(hash), nil
}
@ -82,6 +203,94 @@ func (r *Router) allStickersForKind(ctx context.Context, hash int64, kind domain
return &tg.MessagesAllStickers{Hash: catalogHash, Sets: tgStickerSets(sets)}, nil
}
// featuredCoversPerSet 限制每个 featured 集解析的封面贴纸数量(trending 预览用)。
const featuredCoversPerSet = 5
func (r *Router) onMessagesGetFeaturedStickers(ctx context.Context, hash int64) (tg.MessagesFeaturedStickersClass, error) {
return r.featuredStickersForKind(ctx, hash, domain.StickerSetKindStickers)
}
func (r *Router) onMessagesGetFeaturedEmojiStickers(ctx context.Context, hash int64) (tg.MessagesFeaturedStickersClass, error) {
return r.featuredStickersForKind(ctx, hash, domain.StickerSetKindEmoji)
}
// featuredStickersForKind 把已 seed 的(未归档)贴纸/emoji 集作为 trending 呈现。
// 性能:先用集目录 hash 比对,命中即返回 *NotModified——封面文档解析只在 cache-miss
// 时发生(一次批量 GetDocuments),避免每次请求都解析封面。
func (r *Router) featuredStickersForKind(ctx context.Context, hash int64, kind domain.StickerSetKind) (tg.MessagesFeaturedStickersClass, error) {
if r.deps.Files == nil {
return messagesFeaturedStickersEmpty(hash), nil
}
// perf:从目录缓存读集(TTL 内 hash 命中不打 PG,封面仅 cache-miss 解析)。
sets := r.stickerCatalogSets(ctx, kind)
visible := make([]domain.StickerSet, 0, len(sets))
for _, s := range sets {
if s.ID == 0 || s.Archived {
continue
}
visible = append(visible, s)
}
if len(visible) == 0 {
return messagesFeaturedStickersEmpty(hash), nil
}
catalogHash := stickerSetsCatalogHash(visible)
if hash != 0 && hash == catalogHash {
// 关键 perf 短路:目录未变直接返回,不解析任何封面文档。
return &tg.MessagesFeaturedStickersNotModified{Count: len(visible)}, nil
}
covers := r.resolveFeaturedCovers(ctx, visible)
covered := make([]tg.StickerSetCoveredClass, 0, len(visible))
for _, s := range visible {
covered = append(covered, featuredCoveredSet(s, covers))
}
return &tg.MessagesFeaturedStickers{
Hash: catalogHash,
Count: len(visible),
Sets: covered,
Unread: []int64{},
}, nil
}
// resolveFeaturedCovers 批量解析所有 featured 集的前若干封面文档(一次查询)。
func (r *Router) resolveFeaturedCovers(ctx context.Context, sets []domain.StickerSet) map[int64]domain.Document {
ids := make([]int64, 0, len(sets)*featuredCoversPerSet)
for _, s := range sets {
for i, id := range s.DocumentIDs {
if i >= featuredCoversPerSet {
break
}
if id != 0 {
ids = append(ids, id)
}
}
}
if len(ids) == 0 {
return nil
}
docs, err := r.deps.Files.GetDocuments(ctx, ids)
if err != nil {
return nil
}
return documentsByID(docs)
}
// featuredCoveredSet 用已解析的封面构造 covered set;无封面时回退 noCovered。
func featuredCoveredSet(s domain.StickerSet, covers map[int64]domain.Document) tg.StickerSetCoveredClass {
out := make([]tg.DocumentClass, 0, featuredCoversPerSet)
for i, id := range s.DocumentIDs {
if i >= featuredCoversPerSet {
break
}
if doc, ok := covers[id]; ok {
out = append(out, tgDocument(doc))
}
}
if len(out) == 0 {
return &tg.StickerSetNoCovered{Set: tgStickerSet(s)}
}
return &tg.StickerSetMultiCovered{Set: tgStickerSet(s), Covers: out}
}
func documentsByID(docs []domain.Document) map[int64]domain.Document {
m := make(map[int64]domain.Document, len(docs))
for _, d := range docs {