changed botfather avatar and fixed import
This commit is contained in:
parent
c436c7c773
commit
0a6c05404a
18 changed files with 318 additions and 108 deletions
|
|
@ -291,11 +291,13 @@ type ImportStarGiftRequest struct {
|
|||
|
||||
type ImportDefaultStarGiftRequest struct {
|
||||
CommandMeta
|
||||
ID int `json:"id"`
|
||||
ID int `json:"id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type ImportAllDefaultStarGiftsRequest struct {
|
||||
CommandMeta
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type ImportOfficialStarGiftRequest struct {
|
||||
|
|
@ -317,6 +319,7 @@ type ImportOfficialStarGiftRequest struct {
|
|||
|
||||
type ImportAllOfficialStarGiftsRequest struct {
|
||||
CommandMeta
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type SetStarGiftEnabledRequest struct {
|
||||
|
|
@ -1127,6 +1130,7 @@ func (s *Service) ImportDefaultStarGift(ctx context.Context, req ImportDefaultSt
|
|||
if err != nil {
|
||||
return CommandResult{}, domain.ErrStarGiftInvalid
|
||||
}
|
||||
write.Catalog.Enabled = req.Enabled
|
||||
return s.runCommand(ctx, req.CommandMeta, ActionImportDefaultStarGift, 0, domain.Peer{}, req, func() (CommandResult, error) {
|
||||
details := map[string]any{
|
||||
"id": req.ID, "title": title,
|
||||
|
|
@ -1181,7 +1185,8 @@ func (s *Service) ImportAllDefaultStarGifts(ctx context.Context, req ImportAllDe
|
|||
Actor: req.Actor,
|
||||
Reason: req.Reason,
|
||||
},
|
||||
ID: item.ID,
|
||||
ID: item.ID,
|
||||
Enabled: req.Enabled,
|
||||
}
|
||||
result, opErr := s.ImportDefaultStarGift(ctx, perReq)
|
||||
entry := map[string]any{"id": item.ID, "title": item.Title, "status": result.Status}
|
||||
|
|
@ -1428,6 +1433,7 @@ func (s *Service) ImportAllOfficialStarGifts(ctx context.Context, req ImportAllO
|
|||
// worth importing; CanUpgrade() is exactly the precondition
|
||||
// ImportOfficialStarGift enforces for IncludeCollectible.
|
||||
IncludeCollectible: item.CanUpgrade(),
|
||||
Enabled: req.Enabled,
|
||||
}
|
||||
result, opErr := s.ImportOfficialStarGift(ctx, perReq)
|
||||
entry := map[string]any{"source_gift_id": perReq.SourceGiftID, "status": result.Status}
|
||||
|
|
|
|||
|
|
@ -760,8 +760,8 @@ func TestImportDefaultStarGiftPublishesThroughRealService(t *testing.T) {
|
|||
giftService := stargiftapp.NewService(memory.NewStarGiftStore(), &adminGiftBlob{data: map[string][]byte{}}, 2)
|
||||
svc := NewService(Dependencies{Commands: newMemoryCommandRepo(), Gifts: giftService, Now: fixedNow})
|
||||
|
||||
if list := svc.DefaultStarGifts(); len(list) != 5 {
|
||||
t.Fatalf("default gifts = %d, want 5", len(list))
|
||||
if list := svc.DefaultStarGifts(); len(list) != 3 {
|
||||
t.Fatalf("default gifts = %d, want 3", len(list))
|
||||
}
|
||||
|
||||
// Dry-run must not write to the catalog.
|
||||
|
|
@ -773,15 +773,15 @@ func TestImportDefaultStarGiftPublishesThroughRealService(t *testing.T) {
|
|||
t.Fatalf("dry run wrote %d gifts", len(cat))
|
||||
}
|
||||
|
||||
// Confirmed import-all creates the whole demo set.
|
||||
all := ImportAllDefaultStarGiftsRequest{CommandMeta: CommandMeta{CommandID: "exec-default-all", Actor: "ops", Reason: "demo"}}
|
||||
// Confirmed import-all creates the whole demo set, enabled per the request flag.
|
||||
all := ImportAllDefaultStarGiftsRequest{CommandMeta: CommandMeta{CommandID: "exec-default-all", Actor: "ops", Reason: "demo"}, Enabled: true}
|
||||
result, err := svc.ImportAllDefaultStarGifts(ctx, all)
|
||||
if err != nil || result.Details["imported"] != 5 {
|
||||
if err != nil || result.Details["imported"] != 3 {
|
||||
t.Fatalf("import all: result=%+v err=%v", result, err)
|
||||
}
|
||||
catalog, err := giftService.Catalog(ctx)
|
||||
if err != nil || len(catalog) != 5 {
|
||||
t.Fatalf("catalog=%d err=%v, want 5", len(catalog), err)
|
||||
if err != nil || len(catalog) != 3 {
|
||||
t.Fatalf("catalog=%d err=%v, want 3", len(catalog), err)
|
||||
}
|
||||
limited, premium, upgradeable := 0, 0, 0
|
||||
var craftGiftID int64
|
||||
|
|
@ -799,7 +799,7 @@ func TestImportDefaultStarGiftPublishesThroughRealService(t *testing.T) {
|
|||
craftGiftID = g.ID
|
||||
}
|
||||
}
|
||||
if limited != 2 || premium != 1 || upgradeable != 4 {
|
||||
if limited != 0 || premium != 0 || upgradeable != 2 {
|
||||
t.Fatalf("stored flags limited=%d premium=%d upgradeable=%d", limited, premium, upgradeable)
|
||||
}
|
||||
// The craftable gift must carry a craft-only (named-rarity) model.
|
||||
|
|
@ -818,12 +818,43 @@ func TestImportDefaultStarGiftPublishesThroughRealService(t *testing.T) {
|
|||
}
|
||||
|
||||
// Re-running import-all is idempotent: everything already present -> skipped.
|
||||
result, err = svc.ImportAllDefaultStarGifts(ctx, ImportAllDefaultStarGiftsRequest{CommandMeta: CommandMeta{CommandID: "exec-default-all-2", Actor: "ops", Reason: "demo"}})
|
||||
if err != nil || result.Details["imported"] != 0 || result.Details["skipped"] != 5 {
|
||||
// Enabled must match the first run's value (true) or the per-gift replay
|
||||
// hits COMMAND_ID_CONFLICT since the payload would differ from the cached one.
|
||||
result, err = svc.ImportAllDefaultStarGifts(ctx, ImportAllDefaultStarGiftsRequest{CommandMeta: CommandMeta{CommandID: "exec-default-all-2", Actor: "ops", Reason: "demo"}, Enabled: true})
|
||||
if err != nil || result.Details["imported"] != 0 || result.Details["skipped"] != 3 {
|
||||
t.Fatalf("re-import: result=%+v err=%v", result, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestImportDefaultStarGiftRespectsEnabledFlag is a regression test: a single
|
||||
// default gift import must honor the request's Enabled flag rather than
|
||||
// always landing enabled (or, before this fix, always disabled regardless of
|
||||
// the admin console checkbox).
|
||||
func TestImportDefaultStarGiftRespectsEnabledFlag(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
giftService := stargiftapp.NewService(memory.NewStarGiftStore(), &adminGiftBlob{data: map[string][]byte{}}, 2)
|
||||
svc := NewService(Dependencies{Commands: newMemoryCommandRepo(), Gifts: giftService, Now: fixedNow})
|
||||
|
||||
off := ImportDefaultStarGiftRequest{ID: 1, CommandMeta: CommandMeta{CommandID: "default-1-off", Actor: "ops", Reason: "demo"}, Enabled: false}
|
||||
if _, err := svc.ImportDefaultStarGift(ctx, off); err != nil {
|
||||
t.Fatalf("import disabled: %v", err)
|
||||
}
|
||||
on := ImportDefaultStarGiftRequest{ID: 2, CommandMeta: CommandMeta{CommandID: "default-2-on", Actor: "ops", Reason: "demo"}, Enabled: true}
|
||||
if _, err := svc.ImportDefaultStarGift(ctx, on); err != nil {
|
||||
t.Fatalf("import enabled: %v", err)
|
||||
}
|
||||
|
||||
// Catalog() only ever returns enabled gifts, so presence/absence here
|
||||
// directly proves whether the Enabled flag was honored.
|
||||
catalog, err := giftService.Catalog(ctx)
|
||||
if err != nil || len(catalog) != 1 {
|
||||
t.Fatalf("catalog=%d err=%v, want 1 (only the enabled gift)", len(catalog), err)
|
||||
}
|
||||
if catalog[0].Title != "OwpenGram Star" {
|
||||
t.Fatalf("unexpected gift in catalog: %q, want %q", catalog[0].Title, "OwpenGram Star")
|
||||
}
|
||||
}
|
||||
|
||||
type adminGiftBlob struct{ data map[string][]byte }
|
||||
|
||||
func (b *adminGiftBlob) Name() string { return "localfs" }
|
||||
|
|
|
|||
54
internal/app/files/botfather_avatar_seed.go
Normal file
54
internal/app/files/botfather_avatar_seed.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
//go:embed seedassets/botfather_avatar.jpg
|
||||
var botFatherAvatarJPG []byte
|
||||
|
||||
// SeedBotFatherAvatar idempotently seeds the built-in BotFather account's
|
||||
// profile photo from the bundled avatar, mirroring SeedOfficialSystemAvatar:
|
||||
// writes it under the fixed domain.BotFatherUserPhotoID so the photo/blob
|
||||
// layer and the pure domain.BotFatherUser() 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) SeedBotFatherAvatar(ctx context.Context) (bool, error) {
|
||||
photoID := domain.BotFatherUserPhotoID
|
||||
wrote := false
|
||||
if _, found, err := s.media.GetPhoto(ctx, photoID); err != nil {
|
||||
return false, err
|
||||
} else if !found {
|
||||
sizes, err := s.putPhotoStaticSizes(ctx, photoID, botFatherAvatarJPG, photoSizeSpecsForAvatar(botFatherAvatarJPG))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
photo := domain.Photo{
|
||||
ID: photoID,
|
||||
AccessHash: domain.BotFatherUserPhotoAccessHash,
|
||||
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.BotFatherUserID, photoID, int(time.Now().Unix()))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return false, fmt.Errorf("botfather avatar photo %d not found after seeding", photoID)
|
||||
}
|
||||
domain.SetBotFatherAvatar(photo.DCID, domain.StrippedFromSizes(photo.Sizes))
|
||||
return wrote, nil
|
||||
}
|
||||
BIN
internal/app/files/seedassets/botfather_avatar.jpg
Normal file
BIN
internal/app/files/seedassets/botfather_avatar.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
|
|
@ -15,6 +15,10 @@ const (
|
|||
BotFatherUserID int64 = 93372553
|
||||
// BotFatherAccessHash 固定不变;与迁移 0090 的种子行双写,必须保持一致。
|
||||
BotFatherAccessHash int64 = 7421896403922962293
|
||||
// BotFatherUserPhotoID/AccessHash 是 BotFather 头像 photo 的固定 id,
|
||||
// 与 files.Service.SeedBotFatherAvatar 种子写入的行保持一致。
|
||||
BotFatherUserPhotoID int64 = 933725530001
|
||||
BotFatherUserPhotoAccessHash int64 = 3198475620194837201
|
||||
|
||||
// StickersBotUserID 是内置 @Stickers 账号。它是 server 内置 service bot,
|
||||
// 不走外部 Bot API 进程。
|
||||
|
|
@ -43,6 +47,20 @@ func SetOfficialSystemUserAvatar(dcID int, stripped []byte) {
|
|||
officialSystemUserPhotoStripped = stripped
|
||||
}
|
||||
|
||||
// botFatherPhotoDCID/Stripped 由 files.Service.SeedBotFatherAvatar 在启动时
|
||||
// 通过 SetBotFatherAvatar 写入一次;写入前 BotFatherUser() 不带头像(PhotoID==0)。
|
||||
var (
|
||||
botFatherPhotoDCID int
|
||||
botFatherPhotoStripped []byte
|
||||
)
|
||||
|
||||
// SetBotFatherAvatar 记录 BotFather 头像所在的 DC 与内联缩略图字节。
|
||||
// 只应在启动阶段、头像 seed 完成后调用一次。
|
||||
func SetBotFatherAvatar(dcID int, stripped []byte) {
|
||||
botFatherPhotoDCID = dcID
|
||||
botFatherPhotoStripped = stripped
|
||||
}
|
||||
|
||||
// OfficialSystemUser 返回第一阶段内置的官方系统账号。
|
||||
func OfficialSystemUser() User {
|
||||
u := User{
|
||||
|
|
@ -64,7 +82,7 @@ func OfficialSystemUser() User {
|
|||
|
||||
// BotFatherUser 返回内置 BotFather 账号。username 不以 bot 结尾属种子例外(与官方一致)。
|
||||
func BotFatherUser() User {
|
||||
return User{
|
||||
u := User{
|
||||
ID: BotFatherUserID,
|
||||
AccessHash: BotFatherAccessHash,
|
||||
FirstName: "BotFather",
|
||||
|
|
@ -73,6 +91,12 @@ func BotFatherUser() User {
|
|||
Bot: true,
|
||||
BotInfoVersion: 1,
|
||||
}
|
||||
if botFatherPhotoDCID != 0 {
|
||||
u.PhotoID = BotFatherUserPhotoID
|
||||
u.PhotoDCID = botFatherPhotoDCID
|
||||
u.PhotoStripped = botFatherPhotoStripped
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// StickersBotUser 返回内置 @Stickers 账号。username 不以 bot 结尾属种子例外(与官方一致)。
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ func newService() *stargifts.Service {
|
|||
|
||||
func TestListDescribesFullGiftSurface(t *testing.T) {
|
||||
list := List()
|
||||
if len(list) != 5 {
|
||||
t.Fatalf("List has %d gifts, want 5", len(list))
|
||||
if len(list) != 3 {
|
||||
t.Fatalf("List has %d gifts, want 3", len(list))
|
||||
}
|
||||
upgradeable, craftable, limited, premium := 0, 0, 0, 0
|
||||
for _, g := range list {
|
||||
|
|
@ -50,7 +50,8 @@ func TestListDescribesFullGiftSurface(t *testing.T) {
|
|||
premium++
|
||||
}
|
||||
}
|
||||
if upgradeable != 4 || craftable != 3 || limited != 2 || premium != 1 {
|
||||
// One plain (Spark), one upgradeable-only (Star), one craftable (Coin).
|
||||
if upgradeable != 2 || craftable != 1 || limited != 0 || premium != 0 {
|
||||
t.Fatalf("surface counts upgradeable=%d craftable=%d limited=%d premium=%d", upgradeable, craftable, limited, premium)
|
||||
}
|
||||
}
|
||||
|
|
@ -79,8 +80,8 @@ func TestBuildBundleImportsEndToEnd(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(catalog) != 5 {
|
||||
t.Fatalf("catalog has %d, want 5", len(catalog))
|
||||
if len(catalog) != 3 {
|
||||
t.Fatalf("catalog has %d, want 3", len(catalog))
|
||||
}
|
||||
limited, premium, upgradeable := 0, 0, 0
|
||||
for _, g := range catalog {
|
||||
|
|
@ -94,7 +95,7 @@ func TestBuildBundleImportsEndToEnd(t *testing.T) {
|
|||
upgradeable++
|
||||
}
|
||||
}
|
||||
if limited != 2 || premium != 1 || upgradeable != 4 {
|
||||
if limited != 0 || premium != 0 || upgradeable != 2 {
|
||||
t.Fatalf("stored flags limited=%d premium=%d upgradeable=%d", limited, premium, upgradeable)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,18 +44,19 @@ func demoBackdrops() []backdropSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// demoGifts returns the five demo gifts in display order.
|
||||
// demoGifts returns the three demo gifts in display order, one per capability
|
||||
// tier: plain (not upgradeable), upgradeable (no crafting), and craftable.
|
||||
func demoGifts() []giftSpec {
|
||||
return []giftSpec{
|
||||
{
|
||||
// #1 — cheapest, plain, not upgradeable.
|
||||
// #1 — Звичайний: cheapest, plain, not upgradeable.
|
||||
title: "OwpenGram Spark",
|
||||
stars: 15,
|
||||
convert: 15,
|
||||
base: burst(8, colGold, motionPulse),
|
||||
},
|
||||
{
|
||||
// #2 — standard upgradeable, no crafting.
|
||||
// #2 — Апгрейдебл: standard upgradeable, no crafting.
|
||||
title: "OwpenGram Star",
|
||||
stars: 50,
|
||||
convert: 50,
|
||||
|
|
@ -76,7 +77,7 @@ func demoGifts() []giftSpec {
|
|||
},
|
||||
},
|
||||
{
|
||||
// #3 — upgradeable + craftable.
|
||||
// #3 — Крафтабл: upgradeable + craftable.
|
||||
title: "OwpenGram Coin",
|
||||
stars: 100,
|
||||
convert: 75,
|
||||
|
|
@ -97,54 +98,5 @@ func demoGifts() []giftSpec {
|
|||
backdrops: demoBackdrops(),
|
||||
},
|
||||
},
|
||||
{
|
||||
// #4 — limited edition, upgradeable + craftable.
|
||||
title: "OwpenGram Gem",
|
||||
stars: 250,
|
||||
convert: 200,
|
||||
base: polygon(6, colViolet, colWhite, 10, motionSpinPulse),
|
||||
limited: true,
|
||||
availability: 5000,
|
||||
upgrade: &upgradeSpec{
|
||||
upgradeStars: 800,
|
||||
supplyTotal: 3000,
|
||||
slug: "owg-gem",
|
||||
models: []attrSpec{
|
||||
{name: "Amethyst", spec: polygon(6, colViolet, colWhite, 12, motionSpin), permille: 600},
|
||||
{name: "Verdant", spec: polygon(6, colEmerald, colWhite, 12, motionSpin), permille: 400},
|
||||
{name: "Prism", spec: polygon(8, colCyan, colWhite, 10, motionSpinPulse), crafted: true, rarity: domain.StarGiftRarityEpic},
|
||||
},
|
||||
patterns: []attrSpec{
|
||||
{name: "Facet", spec: burst(12, colViolet, motionPulse), permille: 700},
|
||||
{name: "Shine", spec: burst(8, colWhite, motionPulse), permille: 300},
|
||||
},
|
||||
backdrops: demoBackdrops(),
|
||||
},
|
||||
},
|
||||
{
|
||||
// #5 — premium-gated, limited, the full stack.
|
||||
title: "OwpenGram Crown",
|
||||
stars: 1000,
|
||||
convert: 800,
|
||||
base: star(3, colGold, colAmber, 12, motionSpinPulse),
|
||||
limited: true,
|
||||
availability: 500,
|
||||
requirePremium: true,
|
||||
upgrade: &upgradeSpec{
|
||||
upgradeStars: 2000,
|
||||
supplyTotal: 500,
|
||||
slug: "owg-crown",
|
||||
models: []attrSpec{
|
||||
{name: "Regal", spec: star(3, colGold, colAmber, 14, motionSpinPulse), permille: 600},
|
||||
{name: "Noble", spec: star(5, colAmber, colGold, 12, motionSpin), permille: 400},
|
||||
{name: "Eternal", spec: star(6, colGold, colWhite, 12, motionSpinPulse), crafted: true, rarity: domain.StarGiftRarityLegendary},
|
||||
},
|
||||
patterns: []attrSpec{
|
||||
{name: "Aura", spec: burst(12, colGold, motionPulse), permille: 700},
|
||||
{name: "Crest", spec: burst(8, colWhite, motionPulse), permille: 300},
|
||||
},
|
||||
backdrops: demoBackdrops(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue