feat: sync langpack and auth updates

Sync telesrv commits 49f9bab and 04d9563 into the public mirror. Exclude private docs and runtime key material per sync rules.
This commit is contained in:
A 2026-07-18 00:07:41 +08:00
parent 6af61f26ba
commit 1292540350
244 changed files with 1552172 additions and 27668 deletions

View file

@ -47,6 +47,57 @@ func TestEmailSentCodeUsesDeliveryLength(t *testing.T) {
}
}
func TestAuthSignInRoutesOfficialEmailCodeCarriers(t *testing.T) {
const (
phone = "+86 188 0000 0021"
hash = "hash-email-login"
code = "654321"
)
tests := []struct {
name string
request func() *tg.AuthSignInRequest
wantPhoneCodeCalls int
wantEmailCodeCalls int
}{
{
name: "webk_phone_code",
request: func() *tg.AuthSignInRequest {
return &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: hash, PhoneCode: code}
},
wantPhoneCodeCalls: 1,
},
{
name: "tdesktop_android_email_verification",
request: func() *tg.AuthSignInRequest {
req := &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: hash}
req.SetEmailVerification(&tg.EmailVerificationCode{Code: code})
return req
},
wantEmailCodeCalls: 1,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
authSvc := &captureAuthService{signInUser: domain.User{ID: 100200301, Phone: "8618800000021", FirstName: "Alice"}}
r := New(Config{}, Deps{Auth: authSvc}, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000000, 0)})
if _, err := r.onAuthSignIn(context.Background(), tc.request()); err != nil {
t.Fatalf("onAuthSignIn: %v", err)
}
if authSvc.signInCount != tc.wantPhoneCodeCalls || authSvc.signInWithEmailCount != tc.wantEmailCodeCalls {
t.Fatalf("SignIn/SignInWithEmail calls=%d/%d, want %d/%d", authSvc.signInCount, authSvc.signInWithEmailCount, tc.wantPhoneCodeCalls, tc.wantEmailCodeCalls)
}
if tc.wantPhoneCodeCalls == 1 && (authSvc.signInPhone != phone || authSvc.signInHash != hash || authSvc.signInCode != code) {
t.Fatalf("SignIn proof=%q/%q/%q, want %q/%q/%q", authSvc.signInPhone, authSvc.signInHash, authSvc.signInCode, phone, hash, code)
}
if tc.wantEmailCodeCalls == 1 && (authSvc.signInWithEmailPhone != phone || authSvc.signInWithEmailHash != hash || authSvc.signInWithEmailCode != code) {
t.Fatalf("SignInWithEmail proof=%q/%q/%q, want %q/%q/%q", authSvc.signInWithEmailPhone, authSvc.signInWithEmailHash, authSvc.signInWithEmailCode, phone, hash, code)
}
})
}
}
func TestAccountVerifyEmailLoginSetupReturnsSentCodeSuccess(t *testing.T) {
user := domain.User{
ID: 100200300,

View file

@ -2,11 +2,32 @@ package rpc
import (
"encoding/json"
"telesrv/internal/domain"
"github.com/iamxvbaba/td/tg"
"sort"
"telesrv/internal/domain"
)
func tgLangPackLanguages(items []domain.LangPackLanguage) []tg.LangPackLanguage {
out := make([]tg.LangPackLanguage, 0, len(items))
for _, item := range items {
out = append(out, tg.LangPackLanguage{
Official: item.Official,
Rtl: item.Rtl,
Beta: item.Beta,
Name: item.Name,
NativeName: item.NativeName,
LangCode: item.LangCode,
BaseLangCode: item.BaseLangCode,
PluralCode: item.PluralCode,
StringsCount: item.StringsCount,
TranslatedCount: item.TranslatedCount,
TranslationsURL: item.TranslationsURL,
})
}
return out
}
func tgLangPackStrings(items []domain.LangPackString) []tg.LangPackStringClass {
out := make([]tg.LangPackStringClass, 0, len(items))
for _, item := range items {

View file

@ -775,6 +775,7 @@ type LangPackService interface {
GetLangPack(ctx context.Context, langPack, langCode string) (domain.LangPack, error)
GetDifference(ctx context.Context, langPack, langCode string, fromVersion int) (domain.LangPack, error)
GetStrings(ctx context.Context, langPack, langCode string, keys []string) (domain.LangPack, error)
ListLanguages(ctx context.Context, langPack string) ([]domain.LangPackLanguage, error)
}
// AIComposeService 抽象客户端输入框 AI 改写/润色与 aicompose tones 目录。

View file

@ -26,6 +26,9 @@ func wrapperTooDeepErr() error { return tgerr.New(400, "WRAPPER_TOO_DEEP") }
// inputConstructorInvalidErr 表示客户端传入的 TL 构造器不在当前 RPC 接受范围内。
func inputConstructorInvalidErr() error { return tgerr.New(400, "INPUT_CONSTRUCTOR_INVALID") }
// langCodeNotSupportedErr 表示请求的语言码没有已导入的语言包。
func langCodeNotSupportedErr() error { return tgerr.New(400, "LANG_CODE_NOT_SUPPORTED") }
// folderIDInvalidErr 表示客户端传入多个 folder peer 或非法 folder。
func folderIDInvalidErr() error { return tgerr.New(400, "FOLDER_ID_INVALID") }

View file

@ -17,15 +17,21 @@ func (r *Router) registerLangpack(d *tlprofile.Dispatcher) {
registerRPC[*tg.LangpackGetLanguagesRequest](d, tlprofile.SemanticMethodLangpackGetLanguages, func(ctx context.Context, layerRequest *tg.LangpackGetLanguagesRequest) (any, error) {
langPack := layerRequest.
LangPack
_ = langPack
return r.langpackLanguages(ctx, langPack), nil
languages, err := r.langpackLanguages(ctx, langPack)
if err != nil {
return nil, internalErr()
}
return languages, nil
})
registerRPC[*tg.LangpackGetLanguageRequest](d, tlprofile.SemanticMethodLangpackGetLanguage, func(ctx context.Context, req *tg.LangpackGetLanguageRequest) (any, error) {
if req == nil {
return nil, inputConstructorInvalidErr()
}
lang := r.langpackLanguage(ctx, req.LangPack, req.LangCode)
lang, err := r.langpackLanguage(ctx, req.LangPack, req.LangCode)
if err != nil {
return nil, err
}
return &lang, nil
})
registerRPC[*tg.LangpackGetLangPackRequest](d, tlprofile.SemanticMethodLangpackGetLangPack, func(ctx context.Context, req *tg.LangpackGetLangPackRequest) (any, error) {
@ -71,7 +77,7 @@ func langPackOrClient(ctx context.Context, langPack string) string {
return langPackFromClient(ctx)
}
func (r *Router) langpackLanguage(ctx context.Context, langPack, langCode string) tg.LangPackLanguage {
func (r *Router) langpackLanguage(ctx context.Context, langPack, langCode string) (tg.LangPackLanguage, error) {
if langCode == "" {
if info, ok := ClientInfoFrom(ctx); ok && info.LangCode != "" {
langCode = info.LangCode
@ -80,61 +86,31 @@ func (r *Router) langpackLanguage(ctx context.Context, langPack, langCode string
}
}
langCode = normalizeLangpackCode(langCode)
languages := r.langpackLanguages(ctx, langPack)
languages, err := r.langpackLanguages(ctx, langPack)
if err != nil {
return tg.LangPackLanguage{}, internalErr()
}
for _, lang := range languages {
if strings.ToLower(lang.LangCode) == langCode {
return lang
return lang, nil
}
}
for _, lang := range languages {
if strings.ToLower(lang.PluralCode) == langCode {
return lang
}
}
return languages[0]
return tg.LangPackLanguage{}, langCodeNotSupportedErr()
}
func (r *Router) langpackLanguages(ctx context.Context, langPack string) []tg.LangPackLanguage {
func (r *Router) langpackLanguages(ctx context.Context, langPack string) ([]tg.LangPackLanguage, error) {
if langPack == "" {
langPack = langPackFromClient(ctx)
}
langPack = strings.ToLower(langPack)
languages := []tg.LangPackLanguage{
{
Official: true,
Name: "English",
NativeName: "English",
LangCode: "en",
PluralCode: "en",
StringsCount: 0,
TranslatedCount: 0,
TranslationsURL: "",
},
{
Official: true,
Name: "Chinese (Simplified)",
NativeName: "Chinese (Simplified)",
LangCode: "zh-hans",
PluralCode: "zh",
StringsCount: 0,
TranslatedCount: 0,
TranslationsURL: "",
},
if r.deps.LangPack == nil {
return []tg.LangPackLanguage{}, nil
}
if langPack == "android" {
languages = append(languages, tg.LangPackLanguage{
Official: true,
Rtl: true,
Name: "Persian",
NativeName: "فارسی",
LangCode: "fa",
PluralCode: "fa",
StringsCount: 11002,
TranslatedCount: 11002,
TranslationsURL: "",
})
languages, err := r.deps.LangPack.ListLanguages(ctx, langPack)
if err != nil {
return nil, err
}
return languages
return tgLangPackLanguages(languages), nil
}
func langPackFromClient(ctx context.Context) string {
@ -171,5 +147,6 @@ func normalizeLangpackCode(langCode string) string {
if code == "" {
return "en"
}
code = strings.ReplaceAll(code, "_", "-")
return strings.TrimSuffix(code, "-raw")
}

View file

@ -2,6 +2,7 @@ package rpc
import (
"context"
"strings"
"testing"
"go.uber.org/zap/zaptest"
@ -9,10 +10,14 @@ import (
"github.com/iamxvbaba/td/bin"
"github.com/iamxvbaba/td/clock"
"github.com/iamxvbaba/td/tg"
applangpack "telesrv/internal/app/langpack"
"telesrv/internal/domain"
"telesrv/internal/store/memory"
)
func TestLangpackGetLanguagesCurrentAndLegacy(t *testing.T) {
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
r := newSeededLangpackRouter(t)
t.Run("current layer", func(t *testing.T) {
var in bin.Buffer
@ -47,7 +52,7 @@ func TestLangpackGetLanguagesCurrentAndLegacy(t *testing.T) {
}
func TestLangpackGetLanguage(t *testing.T) {
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
r := newSeededLangpackRouter(t)
var in bin.Buffer
if err := (&tg.LangpackGetLanguageRequest{LangPack: "tdesktop", LangCode: "zh-hans"}).Encode(&in); err != nil {
@ -69,21 +74,33 @@ func TestLangpackGetLanguage(t *testing.T) {
t.Fatalf("language = %+v, want zh-hans", lang)
}
raw := r.langpackLanguage(context.Background(), "tdesktop", "en-raw")
raw, err := r.langpackLanguage(context.Background(), "tdesktop", "en-raw")
if err != nil {
t.Fatalf("language(en-raw): %v", err)
}
if raw.LangCode != "en" {
t.Fatalf("language(en-raw) = %+v, want en", raw)
}
if _, err := r.langpackLanguage(context.Background(), "tdesktop", "zh"); err == nil || !strings.Contains(err.Error(), "LANG_CODE_NOT_SUPPORTED") {
t.Fatalf("language(zh) error = %v, want LANG_CODE_NOT_SUPPORTED", err)
}
}
func TestLangpackAndroidPersianLanguage(t *testing.T) {
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{}, zaptest.NewLogger(t), clock.System)
r := newSeededLangpackRouter(t)
lang := r.langpackLanguage(context.Background(), "android", "fa")
if lang.LangCode != "fa" || lang.PluralCode != "fa" || lang.Name != "Persian" || lang.NativeName != "فارسی" || !lang.Rtl {
lang, err := r.langpackLanguage(context.Background(), "android", "fa")
if err != nil {
t.Fatalf("android fa language: %v", err)
}
if lang.LangCode != "fa" || lang.PluralCode != "fa" || lang.NativeName != "فارسی" || !lang.Rtl {
t.Fatalf("android fa language = %+v", lang)
}
languages := r.langpackLanguages(androidClientContext(), "")
languages, err := r.langpackLanguages(androidClientContext(), "")
if err != nil {
t.Fatalf("android languages: %v", err)
}
for _, item := range languages {
if item.LangCode == "fa" {
return
@ -191,6 +208,53 @@ func TestLegacyAccountRegisterDevice(t *testing.T) {
}
}
func newSeededLangpackRouter(t *testing.T) *Router {
t.Helper()
return New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{
LangPack: seededLangPackService(t),
}, zaptest.NewLogger(t), clock.System)
}
func seededLangPackService(t testing.TB) LangPackService {
t.Helper()
ctx := context.Background()
store := memory.NewLangPackStore()
for _, pack := range []domain.LangPack{
{
LangPack: "tdesktop",
LangCode: "en",
Version: 1,
Strings: []domain.LangPackString{{Key: "lng_language_name", Value: "English"}},
},
{
LangPack: "tdesktop",
LangCode: "zh-hans",
Version: 1,
Strings: []domain.LangPackString{{Key: "lng_language_name", Value: "简体中文"}},
},
{
LangPack: "android",
LangCode: "en",
Version: 1,
Strings: []domain.LangPackString{{Key: "LanguageName", Value: "English"}},
},
{
LangPack: "android",
LangCode: "fa",
Version: 1,
Strings: []domain.LangPackString{
{Key: "LanguageName", Value: "انگلیسی"},
{Key: "TranslateLanguageFA", Value: "فارسی"},
},
},
} {
if err := store.UpsertPack(ctx, pack); err != nil {
t.Fatalf("seed %s/%s: %v", pack.LangPack, pack.LangCode, err)
}
}
return applangpack.NewService(store)
}
func dispatchLangpackLanguages(t *testing.T, r *Router, ctx context.Context, in *bin.Buffer) []tg.LangPackLanguage {
t.Helper()
enc, err := r.Dispatch(ctx, [8]byte{}, 0, in)

View file

@ -1206,6 +1206,7 @@ func TestTDesktopStartupRPCsEncode(t *testing.T) {
LastName: "User",
Phone: "15550000000",
}},
LangPack: seededLangPackService(t),
}, zaptest.NewLogger(t), clock.System)
tests := []struct {

View file

@ -35,7 +35,14 @@ type captureAuthService struct {
completedPasswordKey [8]byte
completePasswordCount int
codeDelivery domain.AuthCodeDelivery
signInCount int
signInPhone string
signInHash string
signInCode string
signInWithEmailCount int
signInWithEmailPhone string
signInWithEmailHash string
signInWithEmailCode string
}
type blockingUserAuthService struct {
@ -188,15 +195,22 @@ func (s *captureAuthService) CancelCode(context.Context, string, string) error {
return nil
}
func (s *captureAuthService) SignIn(context.Context, domain.Authorization, string, string, string) (domain.User, domain.Message, bool, error) {
func (s *captureAuthService) SignIn(_ context.Context, _ domain.Authorization, phone, hash, code string) (domain.User, domain.Message, bool, error) {
s.signInCount++
s.signInPhone = phone
s.signInHash = hash
s.signInCode = code
if s.signInUser.ID != 0 {
return s.signInUser, domain.Message{}, false, nil
}
return domain.User{}, domain.Message{}, false, nil
}
func (s *captureAuthService) SignInWithEmail(context.Context, domain.Authorization, string, string, string) (domain.User, domain.Message, bool, error) {
func (s *captureAuthService) SignInWithEmail(_ context.Context, _ domain.Authorization, phone, hash, code string) (domain.User, domain.Message, bool, error) {
s.signInWithEmailCount++
s.signInWithEmailPhone = phone
s.signInWithEmailHash = hash
s.signInWithEmailCode = code
if s.signInUser.ID != 0 {
return s.signInUser, domain.Message{}, false, nil
}