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 d641725622
commit 11dd7660c0
5 changed files with 336 additions and 15 deletions

View file

@ -392,9 +392,127 @@ func apiChatFull(chat domain.BotAPIChat) map[string]any {
if chat.Fake {
out["is_fake"] = true
}
if chat.SlowModeDelay > 0 {
out["slow_mode_delay"] = chat.SlowModeDelay
}
if chat.LinkedChatID != 0 {
out["linked_chat_id"] = -1000000000000 - chat.LinkedChatID
}
if chat.Permissions != nil {
out["permissions"] = apiChatPermissions(*chat.Permissions)
}
if chat.PinnedMessage != nil {
out["pinned_message"] = apiMessage(*chat.PinnedMessage, chat.PinnedMessageUsers)
}
return out
}
// apiChatPermissions projects a channel's default restrictions as a Bot API
// ChatPermissions object (a right is granted when the matching restriction is
// off).
func apiChatPermissions(b domain.ChannelBannedRights) map[string]any {
text := !b.SendMessages && !b.SendPlain
return map[string]any{
"can_send_messages": text,
"can_send_audios": !b.SendMedia && !b.SendAudios,
"can_send_documents": !b.SendMedia && !b.SendDocs,
"can_send_photos": !b.SendMedia && !b.SendPhotos,
"can_send_videos": !b.SendMedia && !b.SendVideos,
"can_send_video_notes": !b.SendMedia && !b.SendRoundvideos,
"can_send_voice_notes": !b.SendMedia && !b.SendVoices,
"can_send_polls": !b.SendPolls,
"can_send_other_messages": !b.SendStickers && !b.SendGifs && !b.SendGames && !b.SendInline,
"can_add_web_page_previews": !b.EmbedLinks,
"can_change_info": !b.ChangeInfo,
"can_invite_users": !b.InviteUsers,
"can_pin_messages": !b.PinMessages,
"can_manage_topics": !b.ManageTopics,
}
}
// apiChatMember projects a resolved member as a Bot API ChatMember object.
func apiChatMember(m domain.BotAPIChatMember) map[string]any {
out := map[string]any{
"status": botAPIMemberStatus(m.Member),
"user": apiUser(userOrPlaceholder(m.User, m.Member.UserID)),
}
switch out["status"] {
case "creator":
if m.Member.AdminRights.Anonymous {
out["is_anonymous"] = true
}
if m.Member.Rank != "" {
out["custom_title"] = m.Member.Rank
}
case "administrator":
a := m.Member.AdminRights
out["can_be_edited"] = false
out["is_anonymous"] = a.Anonymous
out["can_manage_chat"] = a.ManageChat
out["can_delete_messages"] = a.DeleteMessages
out["can_manage_video_chats"] = a.ManageCall
out["can_restrict_members"] = a.BanUsers
out["can_promote_members"] = a.AddAdmins
out["can_change_info"] = a.ChangeInfo
out["can_invite_users"] = a.InviteUsers
out["can_post_messages"] = a.PostMessages
out["can_edit_messages"] = a.EditMessages
out["can_pin_messages"] = a.PinMessages
out["can_manage_topics"] = a.ManageTopics
out["can_post_stories"] = a.PostStories
out["can_edit_stories"] = a.EditStories
out["can_delete_stories"] = a.DeleteStories
if m.Member.Rank != "" {
out["custom_title"] = m.Member.Rank
}
case "restricted":
b := m.Member.BannedRights
out["is_member"] = m.Member.Status == domain.ChannelMemberActive
for k, v := range apiChatPermissions(b) {
out[k] = v
}
if b.UntilDate > 0 {
out["until_date"] = b.UntilDate
}
case "kicked":
if m.Member.BannedRights.UntilDate > 0 {
out["until_date"] = m.Member.BannedRights.UntilDate
}
}
return out
}
func userOrPlaceholder(u domain.User, id int64) domain.User {
if u.ID != 0 {
return u
}
return domain.User{ID: id}
}
func botAPIMemberStatus(m domain.ChannelMember) string {
switch {
case m.Role == domain.ChannelRoleCreator:
return "creator"
case m.Status == domain.ChannelMemberKicked, m.Status == domain.ChannelMemberBanned, m.BannedRights.ViewMessages:
return "kicked"
case m.Role == domain.ChannelRoleAdmin:
return "administrator"
case m.Status == domain.ChannelMemberLeft:
return "left"
case botAPIMemberRestricted(m.BannedRights):
return "restricted"
default:
return "member"
}
}
func botAPIMemberRestricted(b domain.ChannelBannedRights) bool {
return b.SendMessages || b.SendMedia || b.SendStickers || b.SendGifs || b.SendGames ||
b.SendInline || b.EmbedLinks || b.SendPolls || b.ChangeInfo || b.InviteUsers ||
b.PinMessages || b.ManageTopics || b.SendPhotos || b.SendVideos || b.SendRoundvideos ||
b.SendAudios || b.SendVoices || b.SendDocs || b.SendPlain || b.SendReactions
}
func apiChat(peer domain.Peer, users map[int64]domain.User) map[string]any {
switch peer.Type {
case domain.PeerTypeUser: