botapi: getChatMemberCount, getChatMember, and a fuller getChat

- getChatMemberCount: channel/supergroup participant count (numeric chat_id).
- getChatMember: resolves a member via GetParticipant, projected to a Bot API
  ChatMember (creator/administrator/restricted/member/left/kicked with the
  matching rights); a user simply not in an accessible chat returns "left".
- getChat now uses the full channel view and adds permissions (from the default
  restrictions), slow_mode_delay, linked_chat_id and pinned_message.

Channel-only methods reject user chat_ids; private chats the bot cannot access
return CHAT_NOT_FOUND.
This commit is contained in:
Astra 2026-09-09 16:26:33 +01:00
parent 71da34607e
commit d9ee8cbee0
5 changed files with 336 additions and 15 deletions

View file

@ -216,6 +216,57 @@ func TestGetChatUsesGateway(t *testing.T) {
}
}
func TestGetChatMemberCountAndMember(t *testing.T) {
bots := &fakeBotAPIBots{profile: domain.BotProfile{BotUserID: 1001, TokenSecret: "secret"}}
gateway := &fakeBotAPIGateway{
memberCount: 7,
member: domain.BotAPIChatMember{
User: domain.User{ID: 500, FirstName: "Ann"},
Member: domain.ChannelMember{
UserID: 500, Role: domain.ChannelRoleAdmin, Status: domain.ChannelMemberActive,
AdminRights: domain.ChannelAdminRights{BanUsers: true, PinMessages: true},
Rank: "mod",
},
},
}
h := (&handler{bots: bots, gateway: gateway}).routes()
rec := performBotAPIRequest(t, h, bots.profile, "getChatMemberCount", `{"chat_id":-1000000000042}`)
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), `"result":7`) {
t.Fatalf("getChatMemberCount status=%d body=%s", rec.Code, rec.Body.String())
}
rec = performBotAPIRequest(t, h, bots.profile, "getChatMember", `{"chat_id":-1000000000042,"user_id":500}`)
if rec.Code != http.StatusOK {
t.Fatalf("getChatMember status=%d body=%s", rec.Code, rec.Body.String())
}
var resp struct {
OK bool `json:"ok"`
Result struct {
Status string `json:"status"`
CustomTitle string `json:"custom_title"`
CanRestrict bool `json:"can_restrict_members"`
CanPromote bool `json:"can_promote_members"`
User struct {
ID int64 `json:"id"`
} `json:"user"`
} `json:"result"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode: %v", err)
}
if !resp.OK || resp.Result.Status != "administrator" || resp.Result.User.ID != 500 ||
resp.Result.CustomTitle != "mod" || !resp.Result.CanRestrict || resp.Result.CanPromote {
t.Fatalf("getChatMember result = %s", rec.Body.String())
}
// user_id is required.
rec = performBotAPIRequest(t, h, bots.profile, "getChatMember", `{"chat_id":-1000000000042}`)
if rec.Code != http.StatusBadRequest || !strings.Contains(rec.Body.String(), "USER_ID_INVALID") {
t.Fatalf("missing user_id status=%d body=%s", rec.Code, rec.Body.String())
}
}
func TestBotCommandsPreserveEphemeralFlag(t *testing.T) {
bots := &fakeBotAPIBots{profile: domain.BotProfile{BotUserID: 1001, TokenSecret: "secret"}}
h := (&handler{bots: bots}).routes()
@ -1497,9 +1548,12 @@ func (f *fakeWebAppService) SavePreparedInlineMessageFromBotAPI(_ context.Contex
type fakeBotAPIGateway struct {
self domain.User
chat domain.BotAPIChat
chatErr error
chatChatID int64
chat domain.BotAPIChat
chatErr error
chatChatID int64
memberCount int
member domain.BotAPIChatMember
memberErr error
updates []domain.UpdateEvent
updateBotID int64
@ -1566,6 +1620,14 @@ func (f *fakeBotAPIGateway) BotAPIChat(_ context.Context, _ int64, chatID int64)
return f.chat, f.chatErr
}
func (f *fakeBotAPIGateway) BotAPIChatMemberCount(context.Context, int64, int64) (int, error) {
return f.memberCount, f.memberErr
}
func (f *fakeBotAPIGateway) BotAPIChatMember(context.Context, int64, int64, int64) (domain.BotAPIChatMember, error) {
return f.member, f.memberErr
}
func (f *fakeBotAPIGateway) BotAPIUpdates(_ context.Context, botID int64, offset int64) ([]domain.UpdateEvent, error) {
f.updateBotID = botID
f.updateOffset = offset