diff --git a/cmd/telesrv/main.go b/cmd/telesrv/main.go index b08a2576..a22fed98 100644 --- a/cmd/telesrv/main.go +++ b/cmd/telesrv/main.go @@ -838,6 +838,11 @@ func run(logger *zap.Logger) error { } else if seeded { logger.Info("@ChatBot avatar seed import complete", zap.Int64("photo_id", domain.ChatBotUserPhotoID)) } + if seeded, err := filesService.SeedVerifyBotAvatar(ctx); err != nil { + return fmt.Errorf("seed verifybot avatar: %w", err) + } else if seeded { + logger.Info("@verifybot avatar seed import complete", zap.Int64("photo_id", domain.VerifyBotUserPhotoID)) + } if stats, err := filesService.WarmCaches(ctx); err != nil { logger.Warn("media resource cache warmup failed", zap.Error(err)) } else if stats.StickerSets > 0 || stats.Documents > 0 || stats.Blobs > 0 { diff --git a/internal/app/files/seedassets/verifybot_avatar.png b/internal/app/files/seedassets/verifybot_avatar.png new file mode 100644 index 00000000..7ee797a1 Binary files /dev/null and b/internal/app/files/seedassets/verifybot_avatar.png differ diff --git a/internal/app/files/verifybot_avatar_seed.go b/internal/app/files/verifybot_avatar_seed.go new file mode 100644 index 00000000..8e77faa4 --- /dev/null +++ b/internal/app/files/verifybot_avatar_seed.go @@ -0,0 +1,54 @@ +package files + +import ( + "context" + _ "embed" + "fmt" + "time" + + "telesrv/internal/domain" +) + +//go:embed seedassets/verifybot_avatar.png +var verifyBotAvatarPNG []byte + +// SeedVerifyBotAvatar idempotently seeds the built-in @verifybot account's +// profile photo from the bundled avatar, mirroring SeedChatBotAvatar: writes +// it under the fixed domain.VerifyBotUserPhotoID so the photo/blob layer and +// the pure domain.VerifyBotUser() struct literal stay in sync across +// restarts, and registers it as the account's *current* profile photo so +// users.getFullUser resolves it too. Returns true if it actually wrote a new +// photo. +func (s *Service) SeedVerifyBotAvatar(ctx context.Context) (bool, error) { + photoID := domain.VerifyBotUserPhotoID + wrote := false + if _, found, err := s.media.GetPhoto(ctx, photoID); err != nil { + return false, err + } else if !found { + sizes, err := s.putPhotoStaticSizes(ctx, photoID, verifyBotAvatarPNG, photoSizeSpecsForAvatar(verifyBotAvatarPNG)) + if err != nil { + return false, err + } + photo := domain.Photo{ + ID: photoID, + AccessHash: domain.VerifyBotUserPhotoAccessHash, + FileReference: randomFileReference(), + Date: int(time.Now().Unix()), + DCID: s.dc, + Sizes: sizes, + } + if err := s.media.PutPhoto(ctx, photo); err != nil { + return false, err + } + wrote = true + } + photo, ok, err := s.SetCurrentProfilePhoto(ctx, domain.PeerTypeUser, domain.VerifyBotUserID, photoID, int(time.Now().Unix())) + if err != nil { + return false, err + } + if !ok { + return false, fmt.Errorf("verifybot avatar photo %d not found after seeding", photoID) + } + domain.SetVerifyBotAvatar(photo.DCID, domain.StrippedFromSizes(photo.Sizes)) + return wrote, nil +} diff --git a/internal/domain/system.go b/internal/domain/system.go index e76e1c0a..39756f4a 100644 --- a/internal/domain/system.go +++ b/internal/domain/system.go @@ -47,6 +47,10 @@ const ( // VerifyBotAccessHash is fixed and double-written with the seed row in // migration 0153; the two must never drift. VerifyBotAccessHash int64 = 7802113947355620887 + // VerifyBotUserPhotoID/AccessHash are the fixed id of @verifybot's avatar + // photo, matching the row files.Service.SeedVerifyBotAvatar seeds. + VerifyBotUserPhotoID int64 = 12500000110001 + VerifyBotUserPhotoAccessHash int64 = 975869468725402752 // VerifierBotUserID is the built-in @marksbot: the first THIRD-PARTY // verifier of a deployment (core.telegram.org/api/bots/verification). It @@ -130,6 +134,23 @@ func SetChatBotAvatar(dcID int, stripped []byte) { chatBotPhotoStripped = stripped } +// verifyBotPhotoDCID/Stripped are written once at startup by +// files.Service.SeedVerifyBotAvatar via SetVerifyBotAvatar; before that, +// VerifyBotUser() carries no photo (PhotoID==0), same as every other +// not-yet-seeded built-in account. +var ( + verifyBotPhotoDCID int + verifyBotPhotoStripped []byte +) + +// SetVerifyBotAvatar records the DC and inline thumbnail bytes for +// @verifybot's avatar. Should only be called once, at startup, after the +// avatar seed completes. +func SetVerifyBotAvatar(dcID int, stripped []byte) { + verifyBotPhotoDCID = dcID + verifyBotPhotoStripped = stripped +} + // OfficialSystemUser 返回第一阶段内置的官方系统账号。 func OfficialSystemUser() User { u := User{ @@ -209,7 +230,7 @@ func ChatBotUser() User { // VerifyBotUser returns the built-in @verifybot account. It is verified itself, // so the applicant sees the same badge on the account that grants it. func VerifyBotUser() User { - return User{ + u := User{ ID: VerifyBotUserID, AccessHash: VerifyBotAccessHash, FirstName: "Verify Bot", @@ -218,6 +239,12 @@ func VerifyBotUser() User { Bot: true, BotInfoVersion: 1, } + if verifyBotPhotoDCID != 0 { + u.PhotoID = VerifyBotUserPhotoID + u.PhotoDCID = verifyBotPhotoDCID + u.PhotoStripped = verifyBotPhotoStripped + } + return u } // VerifierBotUser returns the built-in @marksbot account.