feat: sync visible product branding
Sync telesrv c779c48 (feat(branding): unify visible product naming). Skipped private www files and preserved public README files; mapped orange appearance seed names to public default names.
This commit is contained in:
parent
da6a57e1a3
commit
2848ff0987
34 changed files with 449 additions and 91 deletions
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/branding"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/otpdelivery"
|
||||
"telesrv/internal/store"
|
||||
|
|
@ -79,7 +80,7 @@ func (s *Service) DeleteAccount(ctx context.Context, userID int64, authKeyID [8]
|
|||
}
|
||||
executeAt := now.Add(accountDeletionDelay)
|
||||
message := fmt.Sprintf(
|
||||
"A request was made to delete your Telegram account. If this wasn't you, cancel the request: tg://confirmphone?phone=%s&hash=%s",
|
||||
"A request was made to delete your "+branding.ProductName+" account. If this wasn't you, cancel the request: tg://confirmphone?phone=%s&hash=%s",
|
||||
url.QueryEscape(snapshot.User.Phone), url.QueryEscape(rawToken),
|
||||
)
|
||||
pending, _, err := s.lifecycle.ScheduleAccountDeletion(ctx, domain.ScheduleAccountDeletion{
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/iamxvbaba/td/bin"
|
||||
mtcrypto "github.com/iamxvbaba/td/crypto"
|
||||
|
||||
"telesrv/internal/branding"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/otpdelivery"
|
||||
"telesrv/internal/store"
|
||||
|
|
@ -1435,9 +1436,9 @@ func (s *Service) passwordNeeded(ctx context.Context, userID int64) (bool, error
|
|||
return found && settings.HasPassword, nil
|
||||
}
|
||||
|
||||
const loginMessageTpl = `Login code: %s. Do not give this code to anyone, even if they say they are from Telegram!
|
||||
const loginMessageTpl = `Login code: %s. Do not give this code to anyone, even if they say they are from ` + branding.ProductName + `!
|
||||
|
||||
This code can be used to log in to your Telegram account. We never ask it for anything else.
|
||||
This code can be used to log in to your ` + branding.ProductName + ` account. We never ask it for anything else.
|
||||
|
||||
If you didn't request this code by trying to log in on another device, simply ignore this message.`
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"telesrv/internal/branding"
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ const (
|
|||
botFatherDraftBotUsername = "bot_username"
|
||||
)
|
||||
|
||||
const botFatherHelpText = `I can help you create and manage Telegram bots.
|
||||
const botFatherHelpText = `I can help you create and manage ` + branding.ProductName + ` bots.
|
||||
|
||||
You can control me by sending these commands:
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"hash"
|
||||
|
||||
"telesrv/internal/branding"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/seed/appearance"
|
||||
)
|
||||
|
|
@ -253,7 +254,7 @@ func appearanceDocumentAttributes(in []appearance.DocumentAttribute) []domain.Do
|
|||
if attr.FileName != "" {
|
||||
out = append(out, domain.DocumentAttribute{
|
||||
Kind: domain.DocAttrFilename,
|
||||
FileName: attr.FileName,
|
||||
FileName: branding.UserVisibleText(attr.FileName, ""),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"golang.org/x/sync/singleflight"
|
||||
"golang.org/x/text/unicode/bidi"
|
||||
|
||||
"telesrv/internal/branding"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store"
|
||||
)
|
||||
|
|
@ -18,16 +19,34 @@ type Service struct {
|
|||
languageCache *languageListCache
|
||||
packLoads singleflight.Group
|
||||
languageLoads singleflight.Group
|
||||
publicBaseURL string
|
||||
}
|
||||
|
||||
// Option configures user-visible language-pack projection.
|
||||
type Option func(*Service)
|
||||
|
||||
// WithPublicBaseURL replaces official public hosts embedded in upstream
|
||||
// language-pack values with this deployment's public link root.
|
||||
func WithPublicBaseURL(value string) Option {
|
||||
return func(s *Service) {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
s.publicBaseURL = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewService 创建 langpack 服务。
|
||||
func NewService(packs store.LangPackStore) *Service {
|
||||
return newServiceWithCacheLimits(
|
||||
func NewService(packs store.LangPackStore, opts ...Option) *Service {
|
||||
s := newServiceWithCacheLimits(
|
||||
packs,
|
||||
defaultLangPackCacheMaxBytes,
|
||||
defaultLangPackCacheMaxEntries,
|
||||
defaultLanguageListCacheMaxEntries,
|
||||
)
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func newServiceWithCacheLimits(packs store.LangPackStore, maxBytes int64, maxEntries, languageEntries int) *Service {
|
||||
|
|
@ -35,6 +54,7 @@ func newServiceWithCacheLimits(packs store.LangPackStore, maxBytes int64, maxEnt
|
|||
packs: packs,
|
||||
packCache: newLangPackCache(maxBytes, maxEntries),
|
||||
languageCache: newLanguageListCache(languageEntries),
|
||||
publicBaseURL: branding.DefaultPublicURL,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +155,11 @@ func shouldOverlayWebA(langPack string) bool {
|
|||
func (s *Service) rawPack(ctx context.Context, langPack, langCode string) (domain.LangPack, error) {
|
||||
key := langPackCacheKey{pack: langPack, code: langCode, kind: langPackCacheRaw}
|
||||
return s.cachedPack(ctx, key, func() (domain.LangPack, error) {
|
||||
return s.packs.GetPack(ctx, langPack, langCode, 0)
|
||||
pack, err := s.packs.GetPack(ctx, langPack, langCode, 0)
|
||||
if err != nil {
|
||||
return domain.LangPack{}, err
|
||||
}
|
||||
return s.brandPack(pack), nil
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -218,6 +242,7 @@ func (s *Service) cachedLanguages(ctx context.Context, langPack string) ([]domai
|
|||
}
|
||||
for i := range languages {
|
||||
languages[i] = completeLanguageMetadata(langPack, languages[i])
|
||||
languages[i] = s.brandLanguage(languages[i])
|
||||
}
|
||||
return cachedLanguagesLoadResult{
|
||||
languages: languages,
|
||||
|
|
@ -237,6 +262,27 @@ func (s *Service) cachedLanguages(ctx context.Context, langPack string) ([]domai
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Service) brandPack(pack domain.LangPack) domain.LangPack {
|
||||
for i := range pack.Strings {
|
||||
item := &pack.Strings[i]
|
||||
item.Value = branding.UserVisibleText(item.Value, s.publicBaseURL)
|
||||
item.ZeroValue = branding.UserVisibleText(item.ZeroValue, s.publicBaseURL)
|
||||
item.OneValue = branding.UserVisibleText(item.OneValue, s.publicBaseURL)
|
||||
item.TwoValue = branding.UserVisibleText(item.TwoValue, s.publicBaseURL)
|
||||
item.FewValue = branding.UserVisibleText(item.FewValue, s.publicBaseURL)
|
||||
item.ManyValue = branding.UserVisibleText(item.ManyValue, s.publicBaseURL)
|
||||
item.OtherValue = branding.UserVisibleText(item.OtherValue, s.publicBaseURL)
|
||||
}
|
||||
return pack
|
||||
}
|
||||
|
||||
func (s *Service) brandLanguage(lang domain.LangPackLanguage) domain.LangPackLanguage {
|
||||
lang.Name = branding.UserVisibleText(lang.Name, s.publicBaseURL)
|
||||
lang.NativeName = branding.UserVisibleText(lang.NativeName, s.publicBaseURL)
|
||||
lang.TranslationsURL = branding.UserVisibleText(lang.TranslationsURL, s.publicBaseURL)
|
||||
return lang
|
||||
}
|
||||
|
||||
func (s *Service) flushCaches() {
|
||||
if s == nil {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -74,6 +74,66 @@ func TestServiceNormalizesWebARawLangCode(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestServiceRebrandsEveryLanguagePackProjection(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
base := memory.NewLangPackStore()
|
||||
seed := domain.LangPack{
|
||||
LangPack: "weba",
|
||||
LangCode: "en",
|
||||
Version: 7,
|
||||
Strings: []domain.LangPackString{
|
||||
{Key: "AppName", Value: "Telegram", Pluralized: true, ZeroValue: "No Telegram accounts", OneValue: "One Telegram account", TwoValue: "Two Telegram accounts", FewValue: "Few Telegram accounts", ManyValue: "Many Telegram accounts", OtherValue: "Other Telegram accounts"},
|
||||
{Key: "TranslationLink", Value: "https://translations.telegram.org/en"},
|
||||
{Key: "RuntimeIdentifier", Value: "org.telegram.messenger"},
|
||||
},
|
||||
}
|
||||
if err := base.UpsertPack(ctx, seed); err != nil {
|
||||
t.Fatalf("seed langpack: %v", err)
|
||||
}
|
||||
storeWithMetadata := &metadataLangPackStore{
|
||||
LangPackStore: base,
|
||||
languages: []domain.LangPackLanguage{{
|
||||
LangPack: "weba",
|
||||
LangCode: "en",
|
||||
Name: "Telegram English",
|
||||
NativeName: "Telegram English",
|
||||
TranslationsURL: "https://translations.telegram.org/en",
|
||||
}},
|
||||
}
|
||||
svc := NewService(storeWithMetadata, WithPublicBaseURL("https://chat.example/root/"))
|
||||
|
||||
for name, load := range map[string]func() (domain.LangPack, error){
|
||||
"full": func() (domain.LangPack, error) { return svc.GetLangPack(ctx, "weba", "en") },
|
||||
"difference": func() (domain.LangPack, error) { return svc.GetDifference(ctx, "weba", "en", 1) },
|
||||
"keys": func() (domain.LangPack, error) {
|
||||
return svc.GetStrings(ctx, "weba", "en", []string{"AppName", "TranslationLink", "RuntimeIdentifier"})
|
||||
},
|
||||
} {
|
||||
pack, err := load()
|
||||
if err != nil {
|
||||
t.Fatalf("%s projection: %v", name, err)
|
||||
}
|
||||
appName := findLangPackString(pack.Strings, "AppName")
|
||||
if appName == nil || appName.Value != "Telesrv" || appName.ZeroValue != "No Telesrv accounts" || appName.OneValue != "One Telesrv account" || appName.TwoValue != "Two Telesrv accounts" || appName.FewValue != "Few Telesrv accounts" || appName.ManyValue != "Many Telesrv accounts" || appName.OtherValue != "Other Telesrv accounts" {
|
||||
t.Fatalf("%s AppName = %+v, want all value forms rebranded", name, appName)
|
||||
}
|
||||
if got := stringValue(pack.Strings, "TranslationLink"); got != "https://chat.example/root/en" {
|
||||
t.Fatalf("%s TranslationLink = %q", name, got)
|
||||
}
|
||||
if got := stringValue(pack.Strings, "RuntimeIdentifier"); got != "org.telegram.messenger" {
|
||||
t.Fatalf("%s RuntimeIdentifier = %q, want protocol identifier unchanged", name, got)
|
||||
}
|
||||
}
|
||||
|
||||
languages, err := svc.ListLanguages(ctx, "weba")
|
||||
if err != nil {
|
||||
t.Fatalf("list languages: %v", err)
|
||||
}
|
||||
if len(languages) != 1 || languages[0].Name != "Telesrv English" || languages[0].NativeName != "Telesrv English" || languages[0].TranslationsURL != "https://chat.example/root/en" {
|
||||
t.Fatalf("languages = %+v, want branded metadata", languages)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListLanguagesUsesSeededPacks(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
packs := memory.NewLangPackStore()
|
||||
|
|
@ -286,6 +346,15 @@ type countingLangPackStore struct {
|
|||
listLanguages int
|
||||
}
|
||||
|
||||
type metadataLangPackStore struct {
|
||||
store.LangPackStore
|
||||
languages []domain.LangPackLanguage
|
||||
}
|
||||
|
||||
func (s *metadataLangPackStore) ListLanguages(context.Context, string) ([]domain.LangPackLanguage, error) {
|
||||
return append([]domain.LangPackLanguage(nil), s.languages...), nil
|
||||
}
|
||||
|
||||
func (s *countingLangPackStore) GetPack(ctx context.Context, langPack, langCode string, fromVersion int) (domain.LangPack, error) {
|
||||
s.mu.Lock()
|
||||
s.getPack++
|
||||
|
|
@ -334,3 +403,12 @@ func stringValue(strings []domain.LangPackString, key string) string {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findLangPackString(strings []domain.LangPackString, key string) *domain.LangPackString {
|
||||
for i := range strings {
|
||||
if strings[i].Key == key {
|
||||
return &strings[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/branding"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store"
|
||||
"telesrv/internal/webauthn"
|
||||
|
|
@ -40,7 +41,7 @@ type Option func(*Service)
|
|||
func WithRPName(name string) Option { return func(s *Service) { s.rpName = name } }
|
||||
|
||||
// WithAllowedOrigins 设置允许的 WebAuthn origin 白名单;为空表示不强校验 origin
|
||||
//(服务端通常不预知 Android apk-key-hash origin)。
|
||||
// (服务端通常不预知 Android apk-key-hash origin)。
|
||||
func WithAllowedOrigins(origins []string) Option {
|
||||
return func(s *Service) { s.allowedOrigins = append([]string(nil), origins...) }
|
||||
}
|
||||
|
|
@ -70,7 +71,7 @@ func NewService(creds store.PasskeyStore, challenges store.PasskeyChallengeStore
|
|||
creds: creds,
|
||||
challenges: challenges,
|
||||
rpID: rpID,
|
||||
rpName: "Telegram",
|
||||
rpName: branding.ProductName,
|
||||
dcID: dcID,
|
||||
challengeTTL: defaultChallengeTTL,
|
||||
now: time.Now,
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func TestCreateCatalogRevisionPreservesHistoricalRevision(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
first, err := svc.CreateCatalogRevision(ctx, domain.StarGiftCatalogWrite{
|
||||
Stars: 50, ConvertStars: 25, Enabled: true, SortOrder: 1, Title: "First", Animation: animation,
|
||||
Stars: 50, ConvertStars: 25, Enabled: true, SortOrder: 1, Title: "Telegram Pin", Animation: animation,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create first: %v", err)
|
||||
|
|
@ -84,7 +84,7 @@ func TestCreateCatalogRevisionPreservesHistoricalRevision(t *testing.T) {
|
|||
t.Fatalf("current=%+v found=%v", current, found)
|
||||
}
|
||||
historical, found, _ := svc.GiftRevisionByID(ctx, first.Gift.RevisionID)
|
||||
if !found || historical.Stars != 50 || historical.Title != "First" {
|
||||
if !found || historical.Stars != 50 || historical.Title != "Telesrv Pin" {
|
||||
t.Fatalf("historical=%+v found=%v", historical, found)
|
||||
}
|
||||
if _, err := svc.SetCatalogEnabled(ctx, first.Gift.ID+999, false); !errors.Is(err, domain.ErrStarGiftNotFound) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/branding"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store"
|
||||
)
|
||||
|
|
@ -173,7 +174,7 @@ func (s *Service) CreateCatalogRevision(ctx context.Context, write domain.StarGi
|
|||
if s == nil || s.store == nil || s.blobs == nil {
|
||||
return domain.StarGiftCatalogEntry{}, fmt.Errorf("star gift catalog importer is not configured")
|
||||
}
|
||||
write.Title = strings.TrimSpace(write.Title)
|
||||
write.Title = branding.UserVisibleText(strings.TrimSpace(write.Title), "")
|
||||
if write.Stars <= 0 || write.ConvertStars < 0 || write.ConvertStars > write.Stars ||
|
||||
write.Animation.Width != 512 || write.Animation.Height != 512 || len(write.Animation.TGS) == 0 ||
|
||||
len([]rune(write.Title)) > domain.MaxStarGiftTitleRunes {
|
||||
|
|
@ -239,7 +240,8 @@ func (s *Service) CreateCatalogBundle(ctx context.Context, write domain.StarGift
|
|||
if s == nil || s.store == nil || s.blobs == nil {
|
||||
return domain.StarGiftCatalogBundleResult{}, fmt.Errorf("star gift catalog importer is not configured")
|
||||
}
|
||||
write.Catalog.Title = strings.TrimSpace(write.Catalog.Title)
|
||||
write.Catalog.Title = branding.UserVisibleText(strings.TrimSpace(write.Catalog.Title), "")
|
||||
write.Catalog.AuctionSlug = branding.UserVisibleText(strings.TrimSpace(write.Catalog.AuctionSlug), "")
|
||||
if write.Catalog.Stars <= 0 || write.Catalog.ConvertStars < 0 || write.Catalog.ConvertStars > write.Catalog.Stars ||
|
||||
write.Catalog.Animation.Width != 512 || write.Catalog.Animation.Height != 512 || len(write.Catalog.Animation.TGS) == 0 ||
|
||||
len([]rune(write.Catalog.Title)) > domain.MaxStarGiftTitleRunes {
|
||||
|
|
@ -258,6 +260,9 @@ func (s *Service) CreateCatalogBundle(ctx context.Context, write domain.StarGift
|
|||
}
|
||||
if write.Collectible != nil {
|
||||
write.Collectible.SlugPrefix = strings.ToLower(strings.TrimSpace(write.Collectible.SlugPrefix))
|
||||
brandCollectibleAttributes(write.Collectible.Models)
|
||||
brandCollectibleAttributes(write.Collectible.Patterns)
|
||||
brandCollectibleAttributes(write.Collectible.Backdrops)
|
||||
if write.Collectible.OfficialGiftID != write.Catalog.OfficialGiftID ||
|
||||
!bytes.Equal(write.Collectible.SourceManifestSHA256, write.Catalog.SourceManifestSHA256) {
|
||||
return domain.StarGiftCatalogBundleResult{}, domain.ErrStarGiftCollectibleInvalid
|
||||
|
|
@ -315,6 +320,9 @@ func (s *Service) PublishCollectibleRevision(ctx context.Context, write domain.S
|
|||
if s == nil || s.store == nil {
|
||||
return domain.StarGiftCollectibleRevision{}, fmt.Errorf("star gift collectible store is not configured")
|
||||
}
|
||||
brandCollectibleAttributes(write.Models)
|
||||
brandCollectibleAttributes(write.Patterns)
|
||||
brandCollectibleAttributes(write.Backdrops)
|
||||
revision, err := s.store.PublishCollectibleRevision(ctx, write)
|
||||
if err == nil {
|
||||
s.InvalidateStarGiftCatalog()
|
||||
|
|
@ -330,6 +338,9 @@ func (s *Service) CreateCollectibleRevision(ctx context.Context, write domain.St
|
|||
return domain.StarGiftCollectibleRevision{}, fmt.Errorf("star gift collectible importer is not configured")
|
||||
}
|
||||
write.SlugPrefix = strings.ToLower(strings.TrimSpace(write.SlugPrefix))
|
||||
brandCollectibleAttributes(write.Models)
|
||||
brandCollectibleAttributes(write.Patterns)
|
||||
brandCollectibleAttributes(write.Backdrops)
|
||||
if err := domain.ValidateStarGiftCollectibleDraft(write); err != nil {
|
||||
return domain.StarGiftCollectibleRevision{}, err
|
||||
}
|
||||
|
|
@ -342,6 +353,12 @@ func (s *Service) CreateCollectibleRevision(ctx context.Context, write domain.St
|
|||
return s.PublishCollectibleRevision(ctx, write)
|
||||
}
|
||||
|
||||
func brandCollectibleAttributes(attributes []domain.StarGiftCollectibleAttribute) {
|
||||
for i := range attributes {
|
||||
attributes[i].Name = branding.UserVisibleText(strings.TrimSpace(attributes[i].Name), "")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) materializeCollectibleAttributes(ctx context.Context, attributes []domain.StarGiftCollectibleAttribute) error {
|
||||
for i := range attributes {
|
||||
animation := attributes[i].Animation
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue