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

@ -2,24 +2,23 @@ package rpc
import (
"context"
"time"
"github.com/gotd/td/bin"
"github.com/gotd/td/tg"
"telesrv/internal/domain"
)
const updatesTooLongNudgeDelay = 300 * time.Millisecond
const legacyUpdatesGetDifferenceTypeID uint32 = 0x25939651
// registerUpdates 注册 updates.* RPC handler。
func (r *Router) registerUpdates(d *tg.ServerDispatcher) {
d.OnUpdatesGetState(r.onUpdatesGetState)
d.OnUpdatesGetDifference(r.onUpdatesGetDifference)
}
// onUpdatesGetState 处理 updates.getState第一阶段返回零状态
// onUpdatesGetState 处理 updates.getState返回账号当前最新连续状态并推进该设备
// 的确认水位。协议语义是客户端宣告「从现在开始同步」,启动期离线数据由
// getDialogs 快照承载——返回设备旧确认水位会让 TDesktop不持久化 pts、每次
// 启动都调 getState在 getDialogs 快照之上重放历史差分,未读重复累计、
// dialog 预览被旧消息抢占。
func (r *Router) onUpdatesGetState(ctx context.Context) (*tg.UpdatesState, error) {
id, _ := AuthKeyIDFrom(ctx)
userID, _, err := r.currentUserID(ctx)
@ -28,21 +27,17 @@ func (r *Router) onUpdatesGetState(ctx context.Context) (*tg.UpdatesState, error
}
if r.deps.Updates == nil {
r.markSessionReceivesUpdates(ctx, userID)
return &tg.UpdatesState{Date: int(r.clock.Now().Unix())}, nil
return &tg.UpdatesState{Date: int(r.clock.Now().Unix()), Qts: r.deviceEncryptedQts(ctx)}, nil
}
st, err := r.deps.Updates.GetState(ctx, id, userID)
if err != nil {
return nil, internalErr()
}
current, err := r.deps.Updates.CurrentState(ctx, userID)
st, err := r.deps.Updates.AcknowledgeCurrentState(ctx, id, userID)
if err != nil {
return nil, internalErr()
}
r.markSessionReceivesUpdates(ctx, userID)
if current.Pts > st.Pts {
r.scheduleCurrentSessionDifferenceNudge(ctx)
}
return ptr(tgUpdateState(st)), nil
// 密聊 qts 是设备级、独立于账号级 pts 引擎:注入当前设备已分配的 qts无密聊设备为 0
out := tgUpdateState(st)
out.Qts = r.deviceEncryptedQts(ctx)
return ptr(out), nil
}
func (r *Router) onUpdatesGetDifference(ctx context.Context, req *tg.UpdatesGetDifferenceRequest) (tg.UpdatesDifferenceClass, error) {
@ -56,6 +51,16 @@ func (r *Router) onUpdatesGetDifference(ctx context.Context, req *tg.UpdatesGetD
r.markSessionReceivesUpdates(ctx, userID)
return &tg.UpdatesDifferenceEmpty{Date: now}, nil
}
// pts_total_limit 是客户端显式请求的 fast-skip差距超限时返回
// differenceTooLong{pts},客户端据此整体重置会话列表而不是串行翻
// 上千页 slice。不传该参数的客户端TDesktop永远不会收到 tooLong。
if limit, ok := req.GetPtsTotalLimit(); ok && limit > 0 && req.Pts > 0 {
current, err := r.deps.Updates.CurrentState(ctx, userID)
if err == nil && current.Pts-req.Pts > limit {
r.markSessionReceivesUpdates(ctx, userID)
return &tg.UpdatesDifferenceTooLong{Pts: current.Pts}, nil
}
}
st, err := r.deps.Updates.GetDifference(ctx, id, userID, domain.UpdateState{
Pts: req.Pts,
Qts: req.Qts,
@ -66,67 +71,97 @@ func (r *Router) onUpdatesGetDifference(ctx context.Context, req *tg.UpdatesGetD
}
r.markSessionReceivesUpdates(ctx, userID)
st.ChannelNudges = r.accountChannelDifferenceNudges(ctx, userID, req.Date)
if len(st.Events) == 0 && len(st.ChannelNudges) == 0 {
// 密聊设备级 qts 消息(独立于账号级 pts 事件):按当前设备 req.Qts 补回。
encMsgs, newQts := r.encryptedDifference(ctx, req.Qts)
// 密聊握手/已读状态事件(无 qts按未投递标记补回 OtherUpdates。
stateUpdates, statePeerUserIDs, stateEventIDs := r.encryptedStateUpdates(ctx, userID)
if len(st.Events) == 0 && len(st.ChannelNudges) == 0 && len(encMsgs) == 0 && len(stateUpdates) == 0 {
return &tg.UpdatesDifferenceEmpty{Date: st.State.Date, Seq: st.State.Seq}, nil
}
st.Events = r.enrichUpdateEvents(ctx, userID, st.Events)
return tgUpdatesDifference(st), nil
}
func (r *Router) handleLegacyUpdatesGetDifference(ctx context.Context, b *bin.Buffer) (bin.Encoder, error) {
if err := b.ConsumeID(legacyUpdatesGetDifferenceTypeID); err != nil {
return nil, err
}
var flags bin.Fields
if err := flags.Decode(b); err != nil {
return nil, err
}
pts, err := b.Int()
if err != nil {
return nil, err
}
var ptsTotalLimit int
if flags.Has(0) {
ptsTotalLimit, err = b.Int()
if err != nil {
return nil, err
diff := r.tgUpdatesDifference(ctx, userID, st)
diff = injectEncryptedMessages(diff, encMsgs, newQts)
diff = r.injectEncryptedOtherUpdates(ctx, userID, diff, stateUpdates, statePeerUserIDs)
if len(stateEventIDs) > 0 {
if deviceKey, ok := businessAuthKeyIDFrom(ctx); ok {
_ = r.deps.SecretChats.MarkStateEventsDelivered(ctx, deviceKey, stateEventIDs)
}
}
date, err := b.Int()
if err != nil {
return nil, err
}
qts, err := b.Int()
if err != nil {
return nil, err
}
return r.onUpdatesGetDifference(ctx, &tg.UpdatesGetDifferenceRequest{
Flags: flags,
Pts: pts,
PtsTotalLimit: ptsTotalLimit,
Date: date,
Qts: qts,
})
return diff, nil
}
func (r *Router) accountChannelDifferenceNudges(ctx context.Context, userID int64, sinceDate int) []domain.ChannelDifferenceNudge {
if r.deps.Channels == nil || userID == 0 || sinceDate <= 0 {
return nil
}
dirty, err := r.deps.Channels.DirtyActiveChannelsForUser(ctx, userID, sinceDate, 0, domain.MaxChannelDifferenceLimit)
if err != nil || len(dirty) == 0 {
return nil
}
out := make([]domain.ChannelDifferenceNudge, 0, len(dirty))
for _, item := range dirty {
if item.ChannelID == 0 {
continue
// 按 channel_id 翻页注入全部 dirty channel 的 nudge仍有总量硬上限
// 避免加入超过一页活跃频道的账号在长离线恢复时漏掉 channel_id 较大的频道。
const maxNudges = 500
out := make([]domain.ChannelDifferenceNudge, 0, domain.MaxChannelDifferenceLimit)
afterChannelID := int64(0)
for len(out) < maxNudges {
dirty, err := r.deps.Channels.DirtyActiveChannelsForUser(ctx, userID, sinceDate, afterChannelID, domain.MaxChannelDifferenceLimit)
if err != nil || len(dirty) == 0 {
break
}
channelIDs := make([]int64, 0, len(dirty))
for _, item := range dirty {
if item.ChannelID != 0 {
channelIDs = append(channelIDs, item.ChannelID)
}
}
viewsByID := make(map[int64]domain.ChannelView, len(channelIDs))
if len(channelIDs) != 0 {
if views, err := r.deps.Channels.GetChannels(ctx, userID, channelIDs); err == nil {
for _, view := range views {
if view.Channel.ID != 0 {
viewsByID[view.Channel.ID] = view
}
}
}
}
for _, item := range dirty {
if item.ChannelID == 0 {
continue
}
nudge := domain.ChannelDifferenceNudge{ChannelID: item.ChannelID, Pts: item.Pts}
if view, ok := viewsByID[item.ChannelID]; ok {
nudge.Channel = &view
}
out = append(out, nudge)
if item.ChannelID > afterChannelID {
afterChannelID = item.ChannelID
}
}
if len(dirty) < domain.MaxChannelDifferenceLimit {
break
}
out = append(out, domain.ChannelDifferenceNudge{ChannelID: item.ChannelID, Pts: item.Pts})
}
return out
}
// maybeMarkSessionReceivesUpdates 把已登录连接发出的裸 RPC未包 invokeWithoutUpdates
// 视为该 session 的 updates 接收声明,对齐官方语义:客户端只在主连接上发裸请求,
// media/temp 连接一律带 invokeWithoutUpdates 包装。已在接收的 session 直接短路,
// 避免每条 RPC 重复同步 channel membership。
func (r *Router) maybeMarkSessionReceivesUpdates(ctx context.Context) {
if invokeWithoutUpdatesFrom(ctx) {
return
}
userID, ok := UserIDFrom(ctx)
if !ok {
return
}
if provider, ok := r.deps.Sessions.(SessionUpdatesStateProvider); ok {
rawAuthKeyID, okRaw := RawAuthKeyIDFrom(ctx)
sessionID, okSess := SessionIDFrom(ctx)
if okRaw && okSess && provider.ReceivesUpdatesForAuthKey(rawAuthKeyID, sessionID) {
return
}
}
r.markSessionReceivesUpdates(ctx, userID)
}
func (r *Router) markSessionReceivesUpdates(ctx context.Context, userID int64) {
if r.deps.Sessions == nil {
return
@ -145,20 +180,4 @@ func (r *Router) markSessionReceivesUpdates(ctx context.Context, userID int64) {
r.deps.Sessions.SetReceivesUpdates(sessionID, true)
}
func (r *Router) scheduleCurrentSessionDifferenceNudge(ctx context.Context) {
pushCtx := context.Background()
if sessionID, ok := SessionIDFrom(ctx); ok {
pushCtx = WithSessionID(pushCtx, sessionID)
}
if rawAuthKeyID, ok := RawAuthKeyIDFrom(ctx); ok {
pushCtx = WithRawAuthKeyID(pushCtx, rawAuthKeyID)
}
if authKeyID, ok := AuthKeyIDFrom(ctx); ok {
pushCtx = WithAuthKeyID(pushCtx, authKeyID)
}
time.AfterFunc(updatesTooLongNudgeDelay, func() {
r.pushCurrentSessionMessage(pushCtx, "push updatesTooLong after getState", &tg.UpdatesTooLong{})
})
}
func ptr[T any](v T) *T { return &v }