branding: make product identity runtime-configurable
Adopt upstream owpengram/owpengram-server's branding.Config/Configure/ Current in place of the old package-level constants. This is the piece the earlier merge attempt was blocked on (internal/branding failed to import during that merge). The default identity is unchanged -- every existing ProductName/ProductUsername/... default still reads "OwpenGram" -- so this is a pure capability add: nothing currently calls Configure, and every call site now reads the current snapshot via a function instead of a compile-time constant. Five string templates that concatenated branding.ProductName into a `const` had to become `var`, since a func call is no longer a valid const operand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
7fef8438c5
commit
6649b70d5e
15 changed files with 224 additions and 51 deletions
|
|
@ -80,7 +80,7 @@ func (s *Service) DeleteAccount(ctx context.Context, userID int64, authKeyID [8]
|
||||||
}
|
}
|
||||||
executeAt := now.Add(accountDeletionDelay)
|
executeAt := now.Add(accountDeletionDelay)
|
||||||
message := fmt.Sprintf(
|
message := fmt.Sprintf(
|
||||||
"A request was made to delete your "+branding.ProductName+" 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),
|
url.QueryEscape(snapshot.User.Phone), url.QueryEscape(rawToken),
|
||||||
)
|
)
|
||||||
pending, _, err := s.lifecycle.ScheduleAccountDeletion(ctx, domain.ScheduleAccountDeletion{
|
pending, _, err := s.lifecycle.ScheduleAccountDeletion(ctx, domain.ScheduleAccountDeletion{
|
||||||
|
|
|
||||||
|
|
@ -1535,9 +1535,9 @@ func (s *Service) passwordNeeded(ctx context.Context, userID int64) (bool, error
|
||||||
return found && settings.HasPassword, nil
|
return found && settings.HasPassword, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginMessageTpl = `Login code: %s. Do not give this code to anyone, even if they say they are from ` + branding.ProductName + `!
|
var 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 ` + branding.ProductName + ` 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.`
|
If you didn't request this code by trying to log in on another device, simply ignore this message.`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ const (
|
||||||
maxTelegramLoginCommandsPerMessage = 32
|
maxTelegramLoginCommandsPerMessage = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
const botFatherHelpText = `I can help you create and manage ` + branding.ProductName + ` bots.
|
var botFatherHelpText = `I can help you create and manage ` + branding.ProductName() + ` bots.
|
||||||
|
|
||||||
You can control me by sending these commands:
|
You can control me by sending these commands:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -194,13 +194,13 @@ const (
|
||||||
|
|
||||||
// verifierBotWhatText is the part of /start that is true whether or not an
|
// verifierBotWhatText is the part of /start that is true whether or not an
|
||||||
// operator has activated this bot, so it is said first and unconditionally.
|
// operator has activated this bot, so it is said first and unconditionally.
|
||||||
const verifierBotWhatText = `I hand out THIRD-PARTY verification.
|
var verifierBotWhatText = `I hand out THIRD-PARTY verification.
|
||||||
|
|
||||||
A third-party mark is a verifier's own icon, shown right before the name of a bot, a channel or an account, plus one line of description in its profile. It means "this verifier vouches for this peer" -- nothing more.
|
A third-party mark is a verifier's own icon, shown right before the name of a bot, a channel or an account, plus one line of description in its profile. It means "this verifier vouches for this peer" -- nothing more.
|
||||||
|
|
||||||
It is NOT the official ` + branding.ProductName + ` checkmark. The platform badge is granted by the platform itself (@verifybot collects those applications); a third-party mark is granted by the company running a verifier bot. The two are stored, shown and taken away separately, and neither one implies the other.`
|
It is NOT the official ` + branding.ProductName() + ` checkmark. The platform badge is granted by the platform itself (@verifybot collects those applications); a third-party mark is granted by the company running a verifier bot. The two are stored, shown and taken away separately, and neither one implies the other.`
|
||||||
|
|
||||||
const verifierBotHelpText = `I am a verifier bot. I grant third-party marks: my icon before the name of your bot, channel or account, plus a description in its profile. This is not the official ` + branding.ProductName + ` checkmark.
|
var verifierBotHelpText = `I am a verifier bot. I grant third-party marks: my icon before the name of your bot, channel or account, plus a description in its profile. This is not the official ` + branding.ProductName() + ` checkmark.
|
||||||
|
|
||||||
/start - what a third-party mark is and who grants it
|
/start - what a third-party mark is and who grants it
|
||||||
/verify - apply for the mark
|
/verify - apply for the mark
|
||||||
|
|
@ -1121,7 +1121,7 @@ func verifierSummaryText(state domain.BotChatState, settings domain.BotVerifierS
|
||||||
b.WriteString("\n\nWhy:\n")
|
b.WriteString("\n\nWhy:\n")
|
||||||
b.WriteString(state.Draft[verifierDraftReason])
|
b.WriteString(state.Draft[verifierDraftReason])
|
||||||
b.WriteString("\n\nThis is a third-party mark, not the official ")
|
b.WriteString("\n\nThis is a third-party mark, not the official ")
|
||||||
b.WriteString(branding.ProductName)
|
b.WriteString(branding.ProductName())
|
||||||
b.WriteString(" checkmark, and I do not decide: an operator reads the application and either grants the mark or refuses it. I will message you here either way.")
|
b.WriteString(" checkmark, and I do not decide: an operator reads the application and either grants the mark or refuses it. I will message you here either way.")
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
@ -1170,7 +1170,7 @@ func verifierDecisionText(req domain.CustomVerificationRequest) (string, bool) {
|
||||||
switch req.Status {
|
switch req.Status {
|
||||||
case domain.CustomVerificationApproved:
|
case domain.CustomVerificationApproved:
|
||||||
return fmt.Sprintf("Application #%d is approved: %s now carries my mark -- my icon before the name and my description in the profile.\n\nThis is a third-party mark, not the official %s checkmark. Send /revoke if you ever want it removed.",
|
return fmt.Sprintf("Application #%d is approved: %s now carries my mark -- my icon before the name and my description in the profile.\n\nThis is a third-party mark, not the official %s checkmark. Send /revoke if you ever want it removed.",
|
||||||
req.ID, label, branding.ProductName), true
|
req.ID, label, branding.ProductName()), true
|
||||||
case domain.CustomVerificationRejected:
|
case domain.CustomVerificationRejected:
|
||||||
text := fmt.Sprintf("Application #%d for %s was not approved, so no mark was granted.", req.ID, label)
|
text := fmt.Sprintf("Application #%d for %s was not approved, so no mark was granted.", req.ID, label)
|
||||||
if reason := strings.TrimSpace(req.DecisionReason); reason != "" {
|
if reason := strings.TrimSpace(req.DecisionReason); reason != "" {
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ const (
|
||||||
verifyChoiceBlockedPrefix = "no:"
|
verifyChoiceBlockedPrefix = "no:"
|
||||||
)
|
)
|
||||||
|
|
||||||
const verifyBotStartText = `I collect applications for official ` + branding.ProductName + ` verification: the badge shown next to the name of a channel, supergroup or bot whose identity has been confirmed.
|
var verifyBotStartText = `I collect applications for official ` + branding.ProductName() + ` verification: the badge shown next to the name of a channel, supergroup or bot whose identity has been confirmed.
|
||||||
|
|
||||||
Before you apply, check that the subject of the application:
|
Before you apply, check that the subject of the application:
|
||||||
- is a channel, supergroup or bot with a public @username;
|
- is a channel, supergroup or bot with a public @username;
|
||||||
|
|
@ -126,7 +126,7 @@ This badge is never sold and never granted automatically. A person reads every a
|
||||||
|
|
||||||
Tap the button below, or send /new, to start. Send /help for the full list of commands.`
|
Tap the button below, or send /new, to start. Send /help for the full list of commands.`
|
||||||
|
|
||||||
const verifyBotHelpText = `I collect official ` + branding.ProductName + ` verification applications.
|
var verifyBotHelpText = `I collect official ` + branding.ProductName() + ` verification applications.
|
||||||
|
|
||||||
/new - file a verification application
|
/new - file a verification application
|
||||||
/status - list your applications and their status
|
/status - list your applications and their status
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ func (s *Service) SeedDefaultVerifier(ctx context.Context) (bool, error) {
|
||||||
if _, err := s.GrantVerifier(ctx, domain.BotVerifierSettings{
|
if _, err := s.GrantVerifier(ctx, domain.BotVerifierSettings{
|
||||||
BotID: domain.VerifierBotUserID,
|
BotID: domain.VerifierBotUserID,
|
||||||
IconDocumentID: icon.DocumentID,
|
IconDocumentID: icon.DocumentID,
|
||||||
CompanyName: branding.ProductName,
|
CompanyName: branding.ProductName(),
|
||||||
DefaultDescription: "Bundled reference verifier -- auto-granted on first boot.",
|
DefaultDescription: "Bundled reference verifier -- auto-granted on first boot.",
|
||||||
CanModifyCustomDescription: false,
|
CanModifyCustomDescription: false,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ func newServiceWithCacheLimits(packs store.LangPackStore, maxBytes int64, maxEnt
|
||||||
packs: packs,
|
packs: packs,
|
||||||
packCache: newLangPackCache(maxBytes, maxEntries),
|
packCache: newLangPackCache(maxBytes, maxEntries),
|
||||||
languageCache: newLanguageListCache(languageEntries),
|
languageCache: newLanguageListCache(languageEntries),
|
||||||
publicBaseURL: branding.DefaultPublicURL,
|
publicBaseURL: branding.PublicBaseURL(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ func NewService(creds store.PasskeyStore, challenges store.PasskeyChallengeStore
|
||||||
creds: creds,
|
creds: creds,
|
||||||
challenges: challenges,
|
challenges: challenges,
|
||||||
rpID: rpID,
|
rpID: rpID,
|
||||||
rpName: branding.ProductName,
|
rpName: branding.ProductName(),
|
||||||
dcID: dcID,
|
dcID: dcID,
|
||||||
challengeTTL: defaultChallengeTTL,
|
challengeTTL: defaultChallengeTTL,
|
||||||
now: time.Now,
|
now: time.Now,
|
||||||
|
|
|
||||||
|
|
@ -6,44 +6,133 @@
|
||||||
package branding
|
package branding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"telesrv/internal/links"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// Config is the deployment-wide, user-visible product identity. It is loaded
|
||||||
ProductName = "OwpenGram"
|
// once during process startup; protocol identifiers and client detection
|
||||||
ProductUsername = "owpengram"
|
// tokens deliberately remain outside this structure.
|
||||||
DesktopAppName = "OwpenGram Desktop"
|
type Config struct {
|
||||||
AndroidAppName = "OwpenGram Android"
|
ProductName string
|
||||||
IOSAppName = "OwpenGram iOS"
|
ProductUsername string
|
||||||
MacOSAppName = "OwpenGram macOS"
|
DesktopAppName string
|
||||||
WebAAppName = "OwpenGram Web A"
|
AndroidAppName string
|
||||||
WebKAppName = "OwpenGram Web K"
|
IOSAppName string
|
||||||
PremiumName = "OwpenGram Premium"
|
MacOSAppName string
|
||||||
StarsName = "OwpenGram Stars"
|
WebAAppName string
|
||||||
DefaultPublicURL = "https://owpengram.org"
|
WebKAppName string
|
||||||
|
PremiumName string
|
||||||
|
StarsName string
|
||||||
|
PublicBaseURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultConfig = Config{
|
||||||
|
ProductName: "OwpenGram",
|
||||||
|
ProductUsername: "owpengram",
|
||||||
|
DesktopAppName: "OwpenGram Desktop",
|
||||||
|
AndroidAppName: "OwpenGram Android",
|
||||||
|
IOSAppName: "OwpenGram iOS",
|
||||||
|
MacOSAppName: "OwpenGram macOS",
|
||||||
|
WebAAppName: "OwpenGram Web A",
|
||||||
|
WebKAppName: "OwpenGram Web K",
|
||||||
|
PremiumName: "OwpenGram Premium",
|
||||||
|
StarsName: "OwpenGram Stars",
|
||||||
|
PublicBaseURL: links.DefaultDownloadURL,
|
||||||
|
}
|
||||||
|
configured atomic.Pointer[Config]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultConfig returns a copy of the default product identity.
|
||||||
|
func DefaultConfig() Config { return defaultConfig }
|
||||||
|
|
||||||
|
// Validate normalizes and validates a product identity without installing it.
|
||||||
|
func Validate(cfg Config) (Config, error) {
|
||||||
|
for _, field := range []struct {
|
||||||
|
name string
|
||||||
|
value *string
|
||||||
|
}{
|
||||||
|
{name: "product name", value: &cfg.ProductName},
|
||||||
|
{name: "desktop app name", value: &cfg.DesktopAppName},
|
||||||
|
{name: "Android app name", value: &cfg.AndroidAppName},
|
||||||
|
{name: "iOS app name", value: &cfg.IOSAppName},
|
||||||
|
{name: "macOS app name", value: &cfg.MacOSAppName},
|
||||||
|
{name: "Web A app name", value: &cfg.WebAAppName},
|
||||||
|
{name: "Web K app name", value: &cfg.WebKAppName},
|
||||||
|
{name: "Premium name", value: &cfg.PremiumName},
|
||||||
|
{name: "Stars name", value: &cfg.StarsName},
|
||||||
|
} {
|
||||||
|
normalized, err := validateDisplayName(*field.value)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, fmt.Errorf("%s: %w", field.name, err)
|
||||||
|
}
|
||||||
|
*field.value = normalized
|
||||||
|
}
|
||||||
|
cfg.ProductUsername = strings.TrimPrefix(strings.TrimSpace(cfg.ProductUsername), "@")
|
||||||
|
if !validProductUsername(cfg.ProductUsername) {
|
||||||
|
return Config{}, fmt.Errorf("product username must be 5-32 ASCII username characters and start with a letter")
|
||||||
|
}
|
||||||
|
cfg.ProductUsername = strings.ToLower(cfg.ProductUsername)
|
||||||
|
var err error
|
||||||
|
cfg.PublicBaseURL, err = links.ValidateBaseURL(cfg.PublicBaseURL)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, fmt.Errorf("public base URL: %w", err)
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure installs the validated process-wide identity before services are
|
||||||
|
// constructed. Readers only ever observe complete immutable snapshots.
|
||||||
|
func Configure(cfg Config) error {
|
||||||
|
normalized, err := Validate(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configured.Store(&normalized)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current returns a copy of the installed product identity.
|
||||||
|
func Current() Config {
|
||||||
|
if cfg := configured.Load(); cfg != nil {
|
||||||
|
return *cfg
|
||||||
|
}
|
||||||
|
return defaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProductName() string { return Current().ProductName }
|
||||||
|
func ProductUsername() string { return Current().ProductUsername }
|
||||||
|
func PremiumName() string { return Current().PremiumName }
|
||||||
|
func StarsName() string { return Current().StarsName }
|
||||||
|
func PublicBaseURL() string { return Current().PublicBaseURL }
|
||||||
|
|
||||||
// ClientAppName returns the branded display name for a stored client platform.
|
// ClientAppName returns the branded display name for a stored client platform.
|
||||||
// Stored detection tokens remain unchanged; this is only used at presentation
|
// Stored detection tokens remain unchanged; this is only used at presentation
|
||||||
// boundaries such as account.getAuthorizations.
|
// boundaries such as account.getAuthorizations.
|
||||||
func ClientAppName(platform string) string {
|
func ClientAppName(platform string) string {
|
||||||
|
cfg := Current()
|
||||||
switch strings.ToLower(strings.TrimSpace(platform)) {
|
switch strings.ToLower(strings.TrimSpace(platform)) {
|
||||||
case "android":
|
case "android":
|
||||||
return AndroidAppName
|
return cfg.AndroidAppName
|
||||||
case "ios":
|
case "ios":
|
||||||
return IOSAppName
|
return cfg.IOSAppName
|
||||||
case "macos":
|
case "macos":
|
||||||
return MacOSAppName
|
return cfg.MacOSAppName
|
||||||
case "telegram-tt", "weba":
|
case "telegram-tt", "weba":
|
||||||
return WebAAppName
|
return cfg.WebAAppName
|
||||||
case "tweb", "webk":
|
case "tweb", "webk":
|
||||||
return WebKAppName
|
return cfg.WebKAppName
|
||||||
case "tdesktop", "desktop", "windows":
|
case "tdesktop", "desktop", "windows":
|
||||||
return DesktopAppName
|
return cfg.DesktopAppName
|
||||||
default:
|
default:
|
||||||
return ProductName
|
return cfg.ProductName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,18 +167,49 @@ func UserVisibleText(value, publicBaseURL string) string {
|
||||||
if technicalIDRE.MatchString(value) {
|
if technicalIDRE.MatchString(value) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
return officialBrandRE.ReplaceAllString(value, ProductName)
|
return officialBrandRE.ReplaceAllString(value, ProductName())
|
||||||
}
|
}
|
||||||
|
|
||||||
func publicDestination(raw string) (string, string) {
|
func publicDestination(raw string) (string, string) {
|
||||||
raw = strings.TrimRight(strings.TrimSpace(raw), "/")
|
raw = strings.TrimRight(strings.TrimSpace(raw), "/")
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
raw = DefaultPublicURL
|
raw = PublicBaseURL()
|
||||||
}
|
}
|
||||||
parsed, err := url.Parse(raw)
|
parsed, err := url.Parse(raw)
|
||||||
if err != nil || parsed.Scheme == "" || parsed.Hostname() == "" {
|
if err != nil || parsed.Scheme == "" || parsed.Hostname() == "" {
|
||||||
raw = DefaultPublicURL
|
raw = PublicBaseURL()
|
||||||
parsed, _ = url.Parse(raw)
|
parsed, _ = url.Parse(raw)
|
||||||
}
|
}
|
||||||
return raw, parsed.Host
|
return raw, parsed.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateDisplayName(raw string) (string, error) {
|
||||||
|
name := strings.TrimSpace(raw)
|
||||||
|
if name == "" {
|
||||||
|
return "", fmt.Errorf("must not be empty")
|
||||||
|
}
|
||||||
|
if len([]rune(name)) > 64 {
|
||||||
|
return "", fmt.Errorf("must not exceed 64 characters")
|
||||||
|
}
|
||||||
|
for _, r := range name {
|
||||||
|
if unicode.IsControl(r) {
|
||||||
|
return "", fmt.Errorf("must not contain control characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validProductUsername(username string) bool {
|
||||||
|
if len(username) < 5 || len(username) > 32 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, r := range username {
|
||||||
|
switch {
|
||||||
|
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z':
|
||||||
|
case i > 0 && (r >= '0' && r <= '9' || r == '_'):
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,14 @@ func TestUserVisibleTextRebrandsLocalizedProductNames(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientPresentationNames(t *testing.T) {
|
func TestClientPresentationNames(t *testing.T) {
|
||||||
|
cfg := Current()
|
||||||
for platform, want := range map[string]string{
|
for platform, want := range map[string]string{
|
||||||
"tdesktop": DesktopAppName,
|
"tdesktop": cfg.DesktopAppName,
|
||||||
"android": AndroidAppName,
|
"android": cfg.AndroidAppName,
|
||||||
"ios": IOSAppName,
|
"ios": cfg.IOSAppName,
|
||||||
"macos": MacOSAppName,
|
"macos": cfg.MacOSAppName,
|
||||||
"telegram-tt": WebAAppName,
|
"telegram-tt": cfg.WebAAppName,
|
||||||
"tweb": WebKAppName,
|
"tweb": cfg.WebKAppName,
|
||||||
} {
|
} {
|
||||||
if got := ClientAppName(platform); got != want {
|
if got := ClientAppName(platform); got != want {
|
||||||
t.Fatalf("ClientAppName(%q) = %q, want %q", platform, got, want)
|
t.Fatalf("ClientAppName(%q) = %q, want %q", platform, got, want)
|
||||||
|
|
@ -62,3 +63,55 @@ func TestClientPresentationNames(t *testing.T) {
|
||||||
t.Fatalf("UserVisibleClientPlatform() = %q, want weba", got)
|
t.Fatalf("UserVisibleClientPlatform() = %q, want weba", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigureInstallsCompleteBrandSnapshot(t *testing.T) {
|
||||||
|
previous := Current()
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := Configure(previous); err != nil {
|
||||||
|
t.Fatalf("restore branding: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
cfg := Config{
|
||||||
|
ProductName: "Example Chat",
|
||||||
|
ProductUsername: "@Example_Chat",
|
||||||
|
DesktopAppName: "Example Workstation",
|
||||||
|
AndroidAppName: "Example Droid",
|
||||||
|
IOSAppName: "Example Phone",
|
||||||
|
MacOSAppName: "Example Mac",
|
||||||
|
WebAAppName: "Example Web Alpha",
|
||||||
|
WebKAppName: "Example Web Kappa",
|
||||||
|
PremiumName: "Example Plus",
|
||||||
|
StarsName: "Example Credits",
|
||||||
|
PublicBaseURL: "https://links.example.test/root/",
|
||||||
|
}
|
||||||
|
if err := Configure(cfg); err != nil {
|
||||||
|
t.Fatalf("Configure: %v", err)
|
||||||
|
}
|
||||||
|
if got := Current(); got.ProductUsername != "example_chat" || got.PublicBaseURL != "https://links.example.test/root" {
|
||||||
|
t.Fatalf("Current() = %+v", got)
|
||||||
|
}
|
||||||
|
if got := ClientAppName("android"); got != "Example Droid" {
|
||||||
|
t.Fatalf("ClientAppName(android) = %q", got)
|
||||||
|
}
|
||||||
|
if got := UserVisibleText("Telegram at t.me/example", ""); got != "Example Chat at links.example.test/example" {
|
||||||
|
t.Fatalf("UserVisibleText() = %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateRejectsIncompleteOrUnsafeBranding(t *testing.T) {
|
||||||
|
for name, mutate := range map[string]func(*Config){
|
||||||
|
"blank product": func(cfg *Config) { cfg.ProductName = " " },
|
||||||
|
"control": func(cfg *Config) { cfg.StarsName = "bad\nname" },
|
||||||
|
"username": func(cfg *Config) { cfg.ProductUsername = "3bad" },
|
||||||
|
"public URL": func(cfg *Config) { cfg.PublicBaseURL = "file:///tmp/brand" },
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
cfg := DefaultConfig()
|
||||||
|
mutate(&cfg)
|
||||||
|
if _, err := Validate(cfg); err == nil {
|
||||||
|
t.Fatal("Validate accepted invalid branding")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"telesrv/internal/branding"
|
"telesrv/internal/branding"
|
||||||
)
|
)
|
||||||
|
|
||||||
const officialLoginCodeMessageTemplate = `Login code: %s. Do not give this code to anyone, even if they say they are from ` + branding.ProductName + `!
|
var officialLoginCodeMessageTemplate = `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 ` + branding.ProductName + ` 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.`
|
If you didn't request this code by trying to log in on another device, simply ignore this message.`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,8 +172,8 @@ func OfficialSystemUser() User {
|
||||||
ID: OfficialSystemUserID,
|
ID: OfficialSystemUserID,
|
||||||
AccessHash: 6599886787491911851,
|
AccessHash: 6599886787491911851,
|
||||||
Phone: "42777",
|
Phone: "42777",
|
||||||
FirstName: branding.ProductName,
|
FirstName: branding.ProductName(),
|
||||||
Username: branding.ProductUsername,
|
Username: branding.ProductUsername(),
|
||||||
Verified: true,
|
Verified: true,
|
||||||
Support: true,
|
Support: true,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ func (r *Router) resolveAIComposeStyleWebPage(ctx context.Context, rawURL string
|
||||||
Hash: aiComposeToneWebPageHash(tone),
|
Hash: aiComposeToneWebPageHash(tone),
|
||||||
Date: int(now.Unix()),
|
Date: int(now.Unix()),
|
||||||
Type: aiComposeToneWebPageType,
|
Type: aiComposeToneWebPageType,
|
||||||
SiteName: branding.ProductName,
|
SiteName: branding.ProductName(),
|
||||||
Title: tone.Title,
|
Title: tone.Title,
|
||||||
Description: tone.Prompt,
|
Description: tone.Prompt,
|
||||||
ComposeToneEmojiID: tone.EmojiID,
|
ComposeToneEmojiID: tone.EmojiID,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func (r *Router) registerHelp(d *tlprofile.Dispatcher) {
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
registerRPC[*tg.HelpGetInviteTextRequest](d, tlprofile.SemanticMethodHelpGetInviteText, func(ctx context.Context, layerRequest *tg.HelpGetInviteTextRequest) (any, error) {
|
registerRPC[*tg.HelpGetInviteTextRequest](d, tlprofile.SemanticMethodHelpGetInviteText, func(ctx context.Context, layerRequest *tg.HelpGetInviteTextRequest) (any, error) {
|
||||||
return &tg.HelpInviteText{Message: "Join me on " + branding.ProductName + "."}, nil
|
return &tg.HelpInviteText{Message: "Join me on " + branding.ProductName() + "."}, nil
|
||||||
})
|
})
|
||||||
registerRPC[*tg.HelpSaveAppLogRequest](d, tlprofile.SemanticMethodHelpSaveAppLog, func(ctx context.Context, _ *tg.HelpSaveAppLogRequest) (any, error) {
|
registerRPC[*tg.HelpSaveAppLogRequest](d, tlprofile.SemanticMethodHelpSaveAppLog, func(ctx context.Context, _ *tg.HelpSaveAppLogRequest) (any, error) {
|
||||||
return r.onHelpSaveAppLog(ctx)
|
return r.onHelpSaveAppLog(ctx)
|
||||||
|
|
@ -186,7 +186,7 @@ func (r *Router) onHelpDismissSuggestion(ctx context.Context, req *tg.HelpDismis
|
||||||
// dead payment URLs. All six TL fields are mandatory.
|
// dead payment URLs. All six TL fields are mandatory.
|
||||||
func (r *Router) onHelpGetPremiumPromo(ctx context.Context) (*tg.HelpPremiumPromo, error) {
|
func (r *Router) onHelpGetPremiumPromo(ctx context.Context) (*tg.HelpPremiumPromo, error) {
|
||||||
promo := &tg.HelpPremiumPromo{
|
promo := &tg.HelpPremiumPromo{
|
||||||
StatusText: branding.PremiumName + " is not active on this account.",
|
StatusText: branding.PremiumName() + " is not active on this account.",
|
||||||
StatusEntities: []tg.MessageEntityClass{},
|
StatusEntities: []tg.MessageEntityClass{},
|
||||||
VideoSections: []string{},
|
VideoSections: []string{},
|
||||||
Videos: []tg.DocumentClass{},
|
Videos: []tg.DocumentClass{},
|
||||||
|
|
@ -215,7 +215,7 @@ func (r *Router) onHelpGetPremiumPromo(ctx context.Context) (*tg.HelpPremiumProm
|
||||||
}
|
}
|
||||||
if u.PremiumActiveAt(r.clock.Now().Unix()) {
|
if u.PremiumActiveAt(r.clock.Now().Unix()) {
|
||||||
until := time.Unix(int64(u.PremiumUntil), 0)
|
until := time.Unix(int64(u.PremiumUntil), 0)
|
||||||
promo.StatusText = branding.PremiumName + " is active until " + until.Format("2006-01-02") + "."
|
promo.StatusText = branding.PremiumName() + " is active until " + until.Format("2006-01-02") + "."
|
||||||
}
|
}
|
||||||
if r.deps.PremiumPromo != nil {
|
if r.deps.PremiumPromo != nil {
|
||||||
catalog, found, err := r.deps.PremiumPromo.PremiumPromo(ctx)
|
catalog, found, err := r.deps.PremiumPromo.PremiumPromo(ctx)
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ func BuildRegistrationOptions(p RegistrationParams) ([]byte, error) {
|
||||||
exclude = append(exclude, map[string]any{"type": "public-key", "id": b64.EncodeToString(id)})
|
exclude = append(exclude, map[string]any{"type": "public-key", "id": b64.EncodeToString(id)})
|
||||||
}
|
}
|
||||||
pub := map[string]any{
|
pub := map[string]any{
|
||||||
"rp": map[string]any{"id": p.RPID, "name": orDefault(p.RPName, branding.ProductName)},
|
"rp": map[string]any{"id": p.RPID, "name": orDefault(p.RPName, branding.ProductName())},
|
||||||
"user": map[string]any{"id": b64.EncodeToString(p.UserID), "name": p.UserName, "displayName": orDefault(p.UserDisplay, p.UserName)},
|
"user": map[string]any{"id": b64.EncodeToString(p.UserID), "name": p.UserName, "displayName": orDefault(p.UserDisplay, p.UserName)},
|
||||||
"challenge": b64.EncodeToString(p.Challenge),
|
"challenge": b64.EncodeToString(p.Challenge),
|
||||||
"pubKeyCredParams": []map[string]any{
|
"pubKeyCredParams": []map[string]any{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue