rpc: add android startup and messaging compat

(cherry picked from commit d718593156cc7310105007f37645948c17637e0a)
This commit is contained in:
A 2026-06-08 22:30:23 +08:00
parent fc01346160
commit b4e47c894f
11 changed files with 184 additions and 2 deletions

View file

@ -4,12 +4,15 @@ import (
"context"
"errors"
"github.com/gotd/td/bin"
"github.com/gotd/td/tg"
"telesrv/internal/compat/tdesktop"
"telesrv/internal/domain"
)
const legacyAccountRegisterDeviceTypeID uint32 = 0x637ea878
// registerAccount 注册 account.* RPC handler。
func (r *Router) registerAccount(d *tg.ServerDispatcher) {
d.OnAccountRegisterDevice(func(ctx context.Context, req *tg.AccountRegisterDeviceRequest) (bool, error) {
@ -136,6 +139,12 @@ func (r *Router) registerAccount(d *tg.ServerDispatcher) {
}
return &tg.AccountSavedMusicIDs{IDs: []int64{}}, nil
})
d.OnAccountGetSavedRingtones(func(ctx context.Context, hash int64) (tg.AccountSavedRingtonesClass, error) {
if _, _, err := r.currentUserID(ctx); err != nil {
return nil, internalErr()
}
return &tg.AccountSavedRingtones{Hash: 0, Ringtones: []tg.DocumentClass{}}, nil
})
d.OnAccountGetAccountTTL(r.onAccountGetAccountTTL)
d.OnAccountSetAccountTTL(func(ctx context.Context, ttl tg.AccountDaysTTL) (bool, error) {
return true, nil
@ -204,6 +213,23 @@ func (r *Router) onAccountGetPasswordSettings(ctx context.Context, password tg.I
return tgPasswordSettings(settings), nil
}
func (r *Router) handleLegacyAccountRegisterDevice(ctx context.Context, b *bin.Buffer) (bin.Encoder, error) {
if err := b.ConsumeID(legacyAccountRegisterDeviceTypeID); err != nil {
return nil, err
}
if _, err := b.Int(); err != nil {
return nil, err
}
if _, err := b.String(); err != nil {
return nil, err
}
_, _, err := r.currentUserID(ctx)
if err != nil {
return nil, err
}
return &tg.BoolTrue{}, nil
}
func (r *Router) onAccountUpdatePasswordSettings(ctx context.Context, req *tg.AccountUpdatePasswordSettingsRequest) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {

View file

@ -21,6 +21,12 @@ func (r *Router) dispatchCompat(ctx context.Context, b *bin.Buffer, id uint32) (
)
switch id {
case legacyAccountRegisterDeviceTypeID:
name = "account.registerDevice#637ea878"
enc, err = r.handleLegacyAccountRegisterDevice(ctx, b)
case legacyUpdatesGetDifferenceTypeID:
name = "updates.getDifference#25939651"
enc, err = r.handleLegacyUpdatesGetDifference(ctx, b)
case legacyLangpackGetLangPackTypeID:
name = "langpack.getLangPack#9ab5c58e"
enc, err = r.handleLegacyLangpackGetLangPack(ctx, b)

View file

@ -7,6 +7,7 @@ import (
"unicode/utf8"
"github.com/gotd/td/tg"
"go.uber.org/zap"
"telesrv/internal/app/contacts"
"telesrv/internal/compat/tdesktop"
@ -42,6 +43,12 @@ func (r *Router) registerContacts(d *tg.ServerDispatcher) {
return tdesktop.TopPeers(), nil
})
d.OnContactsGetBlocked(r.onContactsGetBlocked)
d.OnContactsGetBirthdays(func(ctx context.Context) (*tg.ContactsContactBirthdays, error) {
if _, _, err := r.currentUserID(ctx); err != nil {
return nil, internalErr()
}
return &tg.ContactsContactBirthdays{Contacts: []tg.ContactBirthday{}, Users: []tg.UserClass{}}, nil
})
d.OnContactsGetSponsoredPeers(func(ctx context.Context, q string) (tg.ContactsSponsoredPeersClass, error) {
if utf8.RuneCountInString(q) > maxContactSearchQLen {
return nil, limitInvalidErr()
@ -250,6 +257,7 @@ func (r *Router) onContactsImportContacts(ctx context.Context, input []tg.InputP
}
res, err := r.deps.Contacts.ImportContacts(ctx, userID, items)
if err != nil {
r.log.Warn("contacts.importContacts service failed", append(r.contextLogFields(ctx), zap.Error(err), zap.Int("contacts", len(items)))...)
return nil, internalErr()
}
out := &tg.ContactsImportedContacts{
@ -267,18 +275,22 @@ func (r *Router) onContactsImportContacts(ctx context.Context, input []tg.InputP
peer := domain.Peer{Type: domain.PeerTypeUser, ID: contact.User.ID}
settings, err := r.deps.Contacts.GetPeerSettings(ctx, userID, peer)
if err != nil {
r.log.Warn("contacts.importContacts peer settings failed", append(r.contextLogFields(ctx), zap.Error(err), zap.Int64("peer_user_id", contact.User.ID))...)
return nil, internalErr()
}
if err := r.recordPeerSettings(ctx, userID, peer, settings); err != nil {
r.log.Warn("contacts.importContacts record peer settings failed", append(r.contextLogFields(ctx), zap.Error(err), zap.Int64("peer_user_id", contact.User.ID))...)
return nil, internalErr()
}
if contact.Mutual {
if err := r.recordAcceptedContactTargetUpdates(ctx, userID, contact.User.ID); err != nil {
r.log.Warn("contacts.importContacts record accepted target failed", append(r.contextLogFields(ctx), zap.Error(err), zap.Int64("peer_user_id", contact.User.ID))...)
return nil, err
}
}
}
if err := r.recordContactsReset(ctx, userID); err != nil {
r.log.Warn("contacts.importContacts record contacts reset failed", append(r.contextLogFields(ctx), zap.Error(err), zap.Int("contacts", len(items)))...)
return nil, internalErr()
}
r.pushContactsReset(ctx, userID)

View file

@ -16,6 +16,9 @@ func (r *Router) registerHelp(d *tg.ServerDispatcher) {
d.OnHelpGetNearestDC(func(ctx context.Context) (*tg.NearestDC, error) {
return tdesktop.NearestDC(r.cfg.DC), nil
})
d.OnHelpGetInviteText(func(ctx context.Context) (*tg.HelpInviteText, error) {
return &tg.HelpInviteText{Message: "Join me on Telegram."}, nil
})
d.OnHelpGetAppConfig(func(ctx context.Context, hash int) (tg.HelpAppConfigClass, error) {
if r.deps.Help == nil {
return tdesktop.AppConfig(hash), nil

View file

@ -79,6 +79,60 @@ func TestLegacyLangpackGetStrings(t *testing.T) {
}
}
func TestLegacyUpdatesGetDifference(t *testing.T) {
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
var flags bin.Fields
flags.Set(0)
var in bin.Buffer
in.PutID(legacyUpdatesGetDifferenceTypeID)
if err := flags.Encode(&in); err != nil {
t.Fatalf("encode flags: %v", err)
}
in.PutInt(10)
in.PutInt(100)
in.PutInt(123456)
in.PutInt(0)
enc, err := r.Dispatch(WithUserID(androidClientContext(), 1000000001), [8]byte{}, 0, &in)
if err != nil {
t.Fatalf("dispatch legacy updates.getDifference: %v", err)
}
var out bin.Buffer
if err := enc.Encode(&out); err != nil {
t.Fatalf("encode response: %v", err)
}
var diff tg.UpdatesDifferenceEmpty
if err := diff.Decode(&out); err != nil {
t.Fatalf("decode response: %v", err)
}
}
func TestLegacyAccountRegisterDevice(t *testing.T) {
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
var in bin.Buffer
in.PutID(legacyAccountRegisterDeviceTypeID)
in.PutInt(2)
in.PutString("android-fcm-token")
enc, err := r.Dispatch(WithUserID(androidClientContext(), 1000000001), [8]byte{}, 0, &in)
if err != nil {
t.Fatalf("dispatch legacy account.registerDevice: %v", err)
}
var out bin.Buffer
if err := enc.Encode(&out); err != nil {
t.Fatalf("encode response: %v", err)
}
ok, err := tg.DecodeBool(&out)
if err != nil {
t.Fatalf("decode response: %v", err)
}
if _, ok := ok.(*tg.BoolTrue); !ok {
t.Fatalf("legacy account.registerDevice = false, want true")
}
}
func assertLangpackLanguages(t *testing.T, r *Router, ctx context.Context, in *bin.Buffer) {
t.Helper()
enc, err := r.Dispatch(ctx, [8]byte{}, 0, in)

View file

@ -183,6 +183,12 @@ func (r *Router) registerMessages(d *tg.ServerDispatcher) {
d.OnMessagesGetStickers(func(ctx context.Context, req *tg.MessagesGetStickersRequest) (tg.MessagesStickersClass, error) {
return tdesktop.Stickers(), nil
})
d.OnMessagesGetArchivedStickers(func(ctx context.Context, req *tg.MessagesGetArchivedStickersRequest) (*tg.MessagesArchivedStickers, error) {
return &tg.MessagesArchivedStickers{
Count: 0,
Sets: []tg.StickerSetCoveredClass{},
}, nil
})
d.OnMessagesGetStickerSet(r.onMessagesGetStickerSet)
d.OnMessagesGetEmojiGroups(func(ctx context.Context, hash int) (tg.MessagesEmojiGroupsClass, error) {
return tdesktop.EmojiGroups(), nil

View file

@ -10,6 +10,17 @@ import (
// registerPayments 注册第一阶段 TDesktop 启动所需 payments.* RPC 兼容响应。
func (r *Router) registerPayments(d *tg.ServerDispatcher) {
d.OnPaymentsGetStarsStatus(func(ctx context.Context, req *tg.PaymentsGetStarsStatusRequest) (*tg.PaymentsStarsStatus, error) {
balance := tg.StarsAmountClass(&tg.StarsAmount{})
if req.GetTon() {
balance = &tg.StarsTonAmount{}
}
return &tg.PaymentsStarsStatus{
Balance: balance,
Chats: []tg.ChatClass{},
Users: []tg.UserClass{},
}, nil
})
d.OnPaymentsGetStarGiftActiveAuctions(func(ctx context.Context, hash int64) (tg.PaymentsStarGiftActiveAuctionsClass, error) {
return tdesktop.StarGiftActiveAuctions(), nil
})

View file

@ -401,6 +401,7 @@ func TestTDesktopStartupRPCsEncode(t *testing.T) {
{name: "help.getPromoData", req: &tg.HelpGetPromoDataRequest{}},
{name: "help.getTermsOfServiceUpdate", req: &tg.HelpGetTermsOfServiceUpdateRequest{}},
{name: "help.getPremiumPromo", req: &tg.HelpGetPremiumPromoRequest{}},
{name: "help.getInviteText", req: &tg.HelpGetInviteTextRequest{}},
{name: "auth.initPasskeyLogin", req: &tg.AuthInitPasskeyLoginRequest{APIID: 4, APIHash: "test"}},
{name: "account.getPassword", req: &tg.AccountGetPasswordRequest{}},
{name: "account.getNotifySettings", req: &tg.AccountGetNotifySettingsRequest{Peer: &tg.InputNotifyUsers{}}},
@ -426,8 +427,10 @@ func TestTDesktopStartupRPCsEncode(t *testing.T) {
{name: "account.getPasskeys", req: &tg.AccountGetPasskeysRequest{}},
{name: "account.getAutoDownloadSettings", req: &tg.AccountGetAutoDownloadSettingsRequest{}},
{name: "account.getSavedMusicIds", req: &tg.AccountGetSavedMusicIDsRequest{}},
{name: "account.getSavedRingtones", req: &tg.AccountGetSavedRingtonesRequest{}},
{name: "account.resetPassword", req: &tg.AccountResetPasswordRequest{}},
{name: "account.updateStatus", req: &tg.AccountUpdateStatusRequest{Offline: true}},
{name: "payments.getStarsStatus", req: &tg.PaymentsGetStarsStatusRequest{Peer: &tg.InputPeerSelf{}}},
{name: "updates.getDifference", req: &tg.UpdatesGetDifferenceRequest{}},
{name: "users.getFullUser", req: &tg.UsersGetFullUserRequest{ID: &tg.InputUserSelf{}}},
{name: "users.getSavedMusic", req: &tg.UsersGetSavedMusicRequest{ID: &tg.InputUserSelf{}, Limit: 20}},
@ -439,6 +442,7 @@ func TestTDesktopStartupRPCsEncode(t *testing.T) {
{name: "messages.getAvailableReactions", req: &tg.MessagesGetAvailableReactionsRequest{}},
{name: "messages.getAvailableEffects", req: &tg.MessagesGetAvailableEffectsRequest{}},
{name: "messages.getStickers", req: &tg.MessagesGetStickersRequest{}},
{name: "messages.getArchivedStickers", req: &tg.MessagesGetArchivedStickersRequest{Limit: 20}},
{name: "messages.getStickerSet", req: &tg.MessagesGetStickerSetRequest{Stickerset: &tg.InputStickerSetEmpty{}}},
{name: "messages.getEmojiGroups", req: &tg.MessagesGetEmojiGroupsRequest{}},
{name: "messages.getEmojiStickerGroups", req: &tg.MessagesGetEmojiStickerGroupsRequest{}},
@ -459,12 +463,15 @@ func TestTDesktopStartupRPCsEncode(t *testing.T) {
{name: "contacts.getContacts", req: &tg.ContactsGetContactsRequest{}},
{name: "contacts.search", req: &tg.ContactsSearchRequest{Q: "Test", Limit: 20}},
{name: "contacts.getBlocked", req: &tg.ContactsGetBlockedRequest{Limit: 20}},
{name: "contacts.getBirthdays", req: &tg.ContactsGetBirthdaysRequest{}},
{name: "contacts.getTopPeers", req: &tg.ContactsGetTopPeersRequest{Correspondents: true, Limit: 10}},
{name: "contacts.getSponsoredPeers", req: &tg.ContactsGetSponsoredPeersRequest{Q: "Test"}},
{name: "stories.getAllStories", req: &tg.StoriesGetAllStoriesRequest{}},
{name: "stories.getStoriesArchive", req: &tg.StoriesGetStoriesArchiveRequest{Peer: &tg.InputPeerSelf{}, Limit: 20}},
{name: "stories.getPinnedStories", req: &tg.StoriesGetPinnedStoriesRequest{Peer: &tg.InputPeerSelf{}, Limit: 20}},
{name: "stories.getAlbums", req: &tg.StoriesGetAlbumsRequest{Peer: &tg.InputPeerSelf{}}},
{name: "stories.getAllReadPeerStories", req: &tg.StoriesGetAllReadPeerStoriesRequest{}},
{name: "stories.getPeerMaxIDs", req: &tg.StoriesGetPeerMaxIDsRequest{ID: []tg.InputPeerClass{&tg.InputPeerSelf{}}}},
{name: "payments.getStarGiftActiveAuctions", req: &tg.PaymentsGetStarGiftActiveAuctionsRequest{}},
{name: "payments.getSavedStarGifts", req: &tg.PaymentsGetSavedStarGiftsRequest{Peer: &tg.InputPeerSelf{}, Limit: 20}},
{name: "payments.getSavedStarGift", req: &tg.PaymentsGetSavedStarGiftRequest{Stargift: []tg.InputSavedStarGiftClass{}}},

View file

@ -23,6 +23,13 @@ func (r *Router) registerStories(d *tg.ServerDispatcher) {
d.OnStoriesGetAlbums(func(ctx context.Context, req *tg.StoriesGetAlbumsRequest) (tg.StoriesAlbumsClass, error) {
return tdesktop.StoryAlbums(), nil
})
d.OnStoriesGetAllReadPeerStories(func(ctx context.Context) (tg.UpdatesClass, error) {
return tgEmptyUpdates(int(r.clock.Now().Unix())), nil
})
d.OnStoriesGetPeerMaxIDs(func(ctx context.Context, id []tg.InputPeerClass) ([]tg.RecentStory, error) {
out := make([]tg.RecentStory, len(id))
return out, nil
})
d.OnStoriesSendReaction(r.onStoriesSendReaction)
}

View file

@ -4,12 +4,14 @@ 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) {
@ -71,6 +73,42 @@ func (r *Router) onUpdatesGetDifference(ctx context.Context, req *tg.UpdatesGetD
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
}
}
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,
})
}
func (r *Router) accountChannelDifferenceNudges(ctx context.Context, userID int64, sinceDate int) []domain.ChannelDifferenceNudge {
if r.deps.Channels == nil || userID == 0 || sinceDate <= 0 {
return nil