Interactive /mybots menu for @BotFather

Button-driven bot management: paginated picker, per-bot API token /
revoke, Edit Bot (name/description/about/commands/botpic), Bot Settings
toggles (inline/groups/privacy), and delete. Navigation edits the menu
message in place via a new editServiceBotMessage helper.

Edit Botpic accepts a photo the user sends to @BotFather and sets it as
the bot's profile photo (new files.SetAvatarFromExistingPhoto, wired
through bots.SetBotUserpic / WithBotAvatarStore); photos.uploadProfilePhoto
does not accept a bot target so this is the only route.
This commit is contained in:
Astra 2026-09-08 13:31:05 +01:00
parent 196f90f9b1
commit f474a360d7
10 changed files with 1467 additions and 26 deletions

View file

@ -869,12 +869,13 @@ func TestVerifyBotCallbackForForeignBotIsNotClaimed(t *testing.T) {
}); handled || err != nil {
t.Fatalf("foreign bot callback handled=%v err=%v, want (false, nil)", handled, err)
}
// A built-in bot with no keyboards is claimed but answered empty, so the click
// cannot hang for the whole callback timeout.
// @BotFather owns the /mybots menu now: a click with no active dialog is
// claimed and answered with the expired-button alert, so it cannot hang for
// the whole callback timeout.
answer, handled, err := svc.OnCallbackQuery(context.Background(), domain.BotCallbackQuery{
BotUserID: domain.BotFatherUserID, UserID: 900, Data: []byte("x"),
BotUserID: domain.BotFatherUserID, UserID: 900, Data: []byte("bf:x"),
})
if !handled || err != nil || answer.Message != "" {
if !handled || err != nil || !answer.Alert || answer.Message == "" {
t.Fatalf("BotFather callback = (%+v, %v, %v)", answer, handled, err)
}
}