Ctrl K
ComunidadGuías…

internal/app/systemidentity/systemidentity.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// 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()
}