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:
parent
1ebf8ddb5a
commit
11142f74c5
10 changed files with 1467 additions and 26 deletions
|
|
@ -31,10 +31,18 @@ func newOwner(t *testing.T, users *memory.UserStore, phone string) domain.User {
|
|||
|
||||
// sendToBotFather 同步驱动 responder(绕过 OnPrivateMessage 的 goroutine 派发以
|
||||
// 保证单测确定性;异步派发由 mtprotoedge bot e2e 覆盖),返回 BotFather 最新回复文本。
|
||||
func botFatherMsg(userID int64, text string) domain.Message {
|
||||
return domain.Message{
|
||||
From: domain.Peer{Type: domain.PeerTypeUser, ID: userID},
|
||||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: domain.BotFatherUserID},
|
||||
Body: text,
|
||||
}
|
||||
}
|
||||
|
||||
func sendToBotFather(t *testing.T, svc *Service, messages *memory.MessageStore, owner domain.User, text string) string {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
svc.respondAsBotFather(owner.ID, text)
|
||||
svc.respondAsBotFather(owner.ID, botFatherMsg(owner.ID, text))
|
||||
list, err := messages.ListByUser(ctx, owner.ID, domain.MessageFilter{
|
||||
HasPeer: true,
|
||||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: domain.BotFatherUserID},
|
||||
|
|
@ -332,8 +340,8 @@ func TestBotFatherMyBotsAndLimit(t *testing.T) {
|
|||
t.Fatalf("create bot %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
if reply := sendToBotFather(t, svc, messages, owner, "/mybots"); !strings.Contains(reply, "@limit0_bot") {
|
||||
t.Fatalf("/mybots reply = %q, want bot list", reply)
|
||||
if reply := sendToBotFather(t, svc, messages, owner, "/mybots"); !strings.Contains(reply, "Choose a bot") {
|
||||
t.Fatalf("/mybots reply = %q, want bot picker", reply)
|
||||
}
|
||||
if _, _, err := svc.CreateBot(ctx, owner.ID, "One Too Many", "toomany_bot"); err != domain.ErrBotsTooMany {
|
||||
t.Fatalf("create over limit err = %v, want ErrBotsTooMany", err)
|
||||
|
|
@ -384,7 +392,7 @@ func TestBotFatherReplyRespectsBlock(t *testing.T) {
|
|||
owner := newOwner(t, users, "+1099")
|
||||
ctx := context.Background()
|
||||
|
||||
svc.respondAsBotFather(owner.ID, "/help")
|
||||
svc.respondAsBotFather(owner.ID, botFatherMsg(owner.ID, "/help"))
|
||||
|
||||
// IsBlocked 参数语义:owner(userID) 是否 block 了 BotFather(blockedUserID)。
|
||||
if blocker.gotUser != owner.ID || blocker.gotPeer != domain.BotFatherUserID {
|
||||
|
|
@ -408,7 +416,7 @@ func TestBotFatherReplyRespectsBlock(t *testing.T) {
|
|||
// 未 block:回复正常投递。
|
||||
blocker.blocked = false
|
||||
other := newOwner(t, users, "+1098")
|
||||
svc.respondAsBotFather(other.ID, "/help")
|
||||
svc.respondAsBotFather(other.ID, botFatherMsg(other.ID, "/help"))
|
||||
otherList, err := messages.ListByUser(ctx, other.ID, domain.MessageFilter{
|
||||
HasPeer: true,
|
||||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: domain.BotFatherUserID},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue