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,251 @@
package rpc
import (
"context"
"github.com/gotd/td/tg"
"telesrv/internal/domain"
)
// 个人贴纸集合:收藏贴纸 / 最近贴纸 / 保存的 GIF。读写都经 stickerCollectionService
// (由 *app/account.Service 实现,类型断言自 r.deps.Account)。文档经 Files 解析为完整
// 文档对象,未接通服务时回落历史空 stub 行为。
type stickerCollectionService interface {
SaveStickerCollectionItem(ctx context.Context, userID int64, kind domain.StickerCollectionKind, documentID int64, unsave bool, now int) error
ListStickerCollection(ctx context.Context, userID int64, kind domain.StickerCollectionKind, limit int) ([]domain.StickerCollectionItem, error)
ClearStickerCollection(ctx context.Context, userID int64, kind domain.StickerCollectionKind) error
}
func (r *Router) stickerCollectionSvc() (stickerCollectionService, bool) {
svc, ok := r.deps.Account.(stickerCollectionService)
return svc, ok
}
// stickerDocumentFromInput 校验输入文档存在且为贴纸(faveSticker/saveRecentSticker)。
func (r *Router) stickerDocumentFromInput(ctx context.Context, input tg.InputDocumentClass, requireGif bool) (domain.Document, error) {
in, ok := input.(*tg.InputDocument)
if !ok || in.ID == 0 || r.deps.Files == nil {
return domain.Document{}, stickerInvalidErr()
}
doc, found, err := r.deps.Files.GetDocument(ctx, in.ID)
if err != nil {
return domain.Document{}, internalErr()
}
ok = found && doc.AccessHash == in.AccessHash
if ok {
if requireGif {
ok = doc.IsGif()
} else {
ok = doc.IsSticker()
}
}
if !ok {
return domain.Document{}, stickerInvalidErr()
}
return doc, nil
}
func (r *Router) onMessagesFaveSticker(ctx context.Context, req *tg.MessagesFaveStickerRequest) (bool, error) {
if req == nil {
return false, stickerInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
doc, err := r.stickerDocumentFromInput(ctx, req.ID, false)
if err != nil {
return false, err
}
if svc, ok := r.stickerCollectionSvc(); ok {
if err := svc.SaveStickerCollectionItem(ctx, userID, domain.StickerCollectionFaved, doc.ID, req.Unfave, int(r.clock.Now().Unix())); err != nil {
return false, internalErr()
}
}
r.pushStickerCollectionUpdate(ctx, userID, &tg.UpdateFavedStickers{})
return true, nil
}
func (r *Router) onMessagesSaveRecentSticker(ctx context.Context, req *tg.MessagesSaveRecentStickerRequest) (bool, error) {
if req == nil {
return false, stickerInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
doc, err := r.stickerDocumentFromInput(ctx, req.ID, false)
if err != nil {
return false, err
}
kind := domain.StickerCollectionRecent
if req.Attached {
kind = domain.StickerCollectionRecentAttached
}
if svc, ok := r.stickerCollectionSvc(); ok {
if err := svc.SaveStickerCollectionItem(ctx, userID, kind, doc.ID, req.Unsave, int(r.clock.Now().Unix())); err != nil {
return false, internalErr()
}
}
r.pushStickerCollectionUpdate(ctx, userID, &tg.UpdateRecentStickers{})
return true, nil
}
func (r *Router) onMessagesSaveGif(ctx context.Context, req *tg.MessagesSaveGifRequest) (bool, error) {
if req == nil {
return false, stickerInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
doc, err := r.stickerDocumentFromInput(ctx, req.ID, true)
if err != nil {
return false, err
}
if svc, ok := r.stickerCollectionSvc(); ok {
if err := svc.SaveStickerCollectionItem(ctx, userID, domain.StickerCollectionGif, doc.ID, req.Unsave, int(r.clock.Now().Unix())); err != nil {
return false, internalErr()
}
}
r.pushStickerCollectionUpdate(ctx, userID, &tg.UpdateSavedGifs{})
return true, nil
}
func (r *Router) onMessagesClearRecentStickers(ctx context.Context, req *tg.MessagesClearRecentStickersRequest) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
kind := domain.StickerCollectionRecent
if req != nil && req.Attached {
kind = domain.StickerCollectionRecentAttached
}
if svc, ok := r.stickerCollectionSvc(); ok {
if err := svc.ClearStickerCollection(ctx, userID, kind); err != nil {
return false, internalErr()
}
}
r.pushStickerCollectionUpdate(ctx, userID, &tg.UpdateRecentStickers{})
return true, nil
}
func (r *Router) onMessagesGetFavedStickers(ctx context.Context, hash int64) (tg.MessagesFavedStickersClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
docs := r.stickerCollectionDocuments(ctx, userID, domain.StickerCollectionFaved, nil)
catalogHash := stickerDocumentsHash(docs)
if hash != 0 && hash == catalogHash {
return &tg.MessagesFavedStickersNotModified{}, nil
}
return &tg.MessagesFavedStickers{
Hash: catalogHash,
Packs: []tg.StickerPack{},
Stickers: tgDocuments(docs),
}, nil
}
func (r *Router) onMessagesGetRecentStickers(ctx context.Context, req *tg.MessagesGetRecentStickersRequest) (tg.MessagesRecentStickersClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
kind := domain.StickerCollectionRecent
if req != nil && req.Attached {
kind = domain.StickerCollectionRecentAttached
}
var dates []int
docs := r.stickerCollectionDocuments(ctx, userID, kind, &dates)
catalogHash := stickerDocumentsHash(docs)
if req != nil && req.Hash != 0 && req.Hash == catalogHash {
return &tg.MessagesRecentStickersNotModified{}, nil
}
return &tg.MessagesRecentStickers{
Hash: catalogHash,
Packs: []tg.StickerPack{},
Stickers: tgDocuments(docs),
Dates: dates,
}, nil
}
func (r *Router) onMessagesGetSavedGifs(ctx context.Context, hash int64) (tg.MessagesSavedGifsClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
docs := r.stickerCollectionDocuments(ctx, userID, domain.StickerCollectionGif, nil)
catalogHash := stickerDocumentsHash(docs)
if hash != 0 && hash == catalogHash {
return &tg.MessagesSavedGifsNotModified{}, nil
}
return &tg.MessagesSavedGifs{Hash: catalogHash, Gifs: tgDocuments(docs)}, nil
}
// stickerCollectionDocuments 取某集合并解析为完整文档(最新在前,顺序与集合一致)。
// 解析不到的文档(已删/不可用)跳过。若 datesOut 非 nil,按相同顺序填充 used_at。
func (r *Router) stickerCollectionDocuments(ctx context.Context, userID int64, kind domain.StickerCollectionKind, datesOut *[]int) []domain.Document {
svc, ok := r.stickerCollectionSvc()
if !ok || r.deps.Files == nil {
if datesOut != nil {
*datesOut = []int{}
}
return nil
}
items, err := svc.ListStickerCollection(ctx, userID, kind, domain.MaxStickerCollectionItems(kind))
if err != nil || len(items) == 0 {
if datesOut != nil {
*datesOut = []int{}
}
return nil
}
ids := make([]int64, 0, len(items))
dateByID := make(map[int64]int, len(items))
for _, it := range items {
ids = append(ids, it.DocumentID)
dateByID[it.DocumentID] = it.Date
}
resolved, err := r.deps.Files.GetDocuments(ctx, ids)
if err != nil {
if datesOut != nil {
*datesOut = []int{}
}
return nil
}
byID := documentsByID(resolved)
docs := make([]domain.Document, 0, len(items))
dates := make([]int, 0, len(items))
for _, id := range ids { // 保持集合顺序(最新在前)
doc, ok := byID[id]
if !ok {
continue
}
docs = append(docs, doc)
dates = append(dates, dateByID[id])
}
if datesOut != nil {
*datesOut = dates
}
return docs
}
func stickerDocumentsHash(docs []domain.Document) int64 {
values := make([]int64, 0, len(docs))
for _, d := range docs {
values = append(values, d.ID)
}
return int64(tdesktopCountHash(values))
}
// pushStickerCollectionUpdate 把 updateFaved/Recent/SavedGifs nudge 推给本人其它在线设备。
func (r *Router) pushStickerCollectionUpdate(ctx context.Context, userID int64, update tg.UpdateClass) {
r.pushUserUpdates(ctx, userID, &tg.Updates{
Updates: []tg.UpdateClass{update},
Users: []tg.UserClass{},
Chats: []tg.ChatClass{},
Date: int(r.clock.Now().Unix()),
})
}