internal/app/systemidentity/systemidentity.go
Rama main · réplica de lectura del repositorio original.
// Package systemidentity applies the server identity configured in the admin panel's
// Server Settings → Server identity to the running main server: the official system
// account's (777000) display name, and, when an icon is configured, its avatar.
//
// The admin binary and the main server binary are two processes that share state only
// through identity.json on disk (see the internal/identity package comment). The main
// server therefore cannot settle for a single read at startup: the name and icon must keep
// following panel changes at runtime, otherwise an operator's save would not take effect
// until the server restarts. The Watcher polls identity.Store at a fixed interval,
// comparing name and icon separately and re-applying only the one that actually changed —
// a name change does not rebuild the avatar, only an icon change does. The login
// notification template already reads live on every send and does not go through here.
package systemidentity
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"telesrv/internal/domain"
"telesrv/internal/identity"
)
// AvatarSeeder seeds the official system account's avatar with the operator's icon bytes
// (empty means fall back to the built-in default avatar) and sets it as the current
// avatar. Usually bound to botavatars.SeedOfficialSystemAvatar on the files service.
type AvatarSeeder func(ctx context.Context, icon []byte, now int64) (bool, error)
// Apply reads the stored identity and projects it onto the running server in one pass:
// the 777000 display name (via domain), and the official system account avatar (when a
// seeder is provided). Called once during startup; incremental runtime updates are left
// to the Watcher.
//
// The name is written even if the seeder fails — the name projection is a cheap,
// independent step and should not be lost along with an avatar-seeding error.
func Apply(ctx context.Context, store *identity.Store, seedAvatar AvatarSeeder, now int64) (identity.Info, error) {
info, err := store.Get()
if err != nil {
return identity.Info{}, err
}
domain.SetOfficialSystemUserDisplayName(info.Name)
if seedAvatar != nil {
if err := seedAvatarFromStore(ctx, store, seedAvatar, now); err != nil {
return info, err
}
}
return info, nil
}
func seedAvatarFromStore(ctx context.Context, store *identity.Store, seedAvatar AvatarSeeder, now int64) error {
var icon []byte
if data, _, ok := store.Icon(); ok {
icon = data
}
if _, err := seedAvatar(ctx, icon, now); err != nil {
return fmt.Errorf("seed official system avatar: %w", err)
}
return nil
}
// Watcher polls identity.Store and re-applies when the name or icon changes. A name change
// only updates the display name, while an icon change rebuilds the 777000 avatar (including
// falling back to the built-in default avatar after the icon is removed).
type Watcher struct {
Store *identity.Store
SeedAvatar AvatarSeeder
// Interval is the polling interval; <= 0 defaults to 5 seconds.
Interval time.Duration
Logger *zap.Logger
}
// Run polls until ctx is cancelled. It records the current name and icon fingerprint
// before entering the poll loop: startup already applied the identity once (see
// cmd/telesrv/main.go), so this only responds to changes from then on.
func (w *Watcher) Run(ctx context.Context) {
interval := w.Interval
if interval <= 0 {
interval = 5 * time.Second
}
name, icon, err := w.snapshot()
if err != nil {
w.log().Warn("server identity snapshot failed", zap.Error(err))
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
newName, newIcon, err := w.snapshot()
if err != nil {
w.log().Warn("server identity snapshot failed", zap.Error(err))
continue
}
if newName != name {
domain.SetOfficialSystemUserDisplayName(newName)
name = newName
w.log().Info("server identity name applied", zap.String("name", newName))
}
if newIcon == icon {
continue
}
if err := seedAvatarFromStore(ctx, w.Store, w.SeedAvatar, time.Now().Unix()); err != nil {
w.log().Warn("apply server identity avatar failed", zap.Error(err))
continue
}
icon = newIcon
w.log().Info("server identity avatar applied")
}
}
}
func (w *Watcher) snapshot() (string, string, error) {
info, err := w.Store.Get()
if err != nil {
return "", "", err
}
icon, err := w.Store.IconFingerprint()
if err != nil {
return "", "", err
}
return info.Name, icon, nil
}
func (w *Watcher) log() *zap.Logger {
if w.Logger != nil {
return w.Logger
}
return zap.NewNop()
}