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

@ -42,6 +42,8 @@ type WebAppService interface {
type GatewayService interface {
BotAPISelf(ctx context.Context, botID int64) (domain.User, error)
BotAPIChat(ctx context.Context, botID, chatID int64) (domain.BotAPIChat, error)
BotAPIChatMemberCount(ctx context.Context, botID, chatID int64) (int, error)
BotAPIChatMember(ctx context.Context, botID, chatID, userID int64) (domain.BotAPIChatMember, error)
BotAPIUpdates(ctx context.Context, botID int64, offset int64) ([]domain.UpdateEvent, error)
BotAPISendMessage(ctx context.Context, botID, chatID int64, text string, entities []domain.MessageEntity, replyMarkup *domain.MessageReplyMarkup, disableWebPagePreview, silent bool, replyToMessageID int) (domain.Message, error)
BotAPISendRichMessage(ctx context.Context, botID, chatID int64, rich domain.BotAPIRichMessageInput, replyMarkup *domain.MessageReplyMarkup, silent, noForwards bool, replyToMessageID int, effectID int64) (domain.Message, error)
@ -200,6 +202,10 @@ func (h *handler) handle(w http.ResponseWriter, r *http.Request) {
h.getMe(w, r, botID)
case "getchat":
h.getChat(w, r, botID)
case "getchatmembercount", "getchatmemberscount":
h.getChatMemberCount(w, r, botID)
case "getchatmember":
h.getChatMember(w, r, botID)
case "setmycommands":
h.setMyCommands(w, r, botID)
case "deletemycommands":
@ -337,6 +343,57 @@ func (h *handler) getChat(w http.ResponseWriter, r *http.Request, botID int64) {
writeAPIOK(w, apiChatFull(chat))
}
func (h *handler) getChatMemberCount(w http.ResponseWriter, r *http.Request, botID int64) {
if h.gateway == nil {
writeAPIError(w, http.StatusNotImplemented, "METHOD_NOT_FOUND")
return
}
values, err := requestValues(r)
if err != nil {
writeAPIError(w, http.StatusBadRequest, "BAD_REQUEST")
return
}
chatID, err := strconv.ParseInt(strings.TrimSpace(values["chat_id"]), 10, 64)
if err != nil || chatID == 0 {
writeAPIError(w, http.StatusBadRequest, "CHAT_ID_INVALID")
return
}
count, err := h.gateway.BotAPIChatMemberCount(r.Context(), botID, chatID)
if err != nil {
writeAPIError(w, http.StatusBadRequest, apiErrorDescription(err))
return
}
writeAPIOK(w, count)
}
func (h *handler) getChatMember(w http.ResponseWriter, r *http.Request, botID int64) {
if h.gateway == nil {
writeAPIError(w, http.StatusNotImplemented, "METHOD_NOT_FOUND")
return
}
values, err := requestValues(r)
if err != nil {
writeAPIError(w, http.StatusBadRequest, "BAD_REQUEST")
return
}
chatID, err := strconv.ParseInt(strings.TrimSpace(values["chat_id"]), 10, 64)
if err != nil || chatID == 0 {
writeAPIError(w, http.StatusBadRequest, "CHAT_ID_INVALID")
return
}
userID, err := strconv.ParseInt(strings.TrimSpace(values["user_id"]), 10, 64)
if err != nil || userID <= 0 {
writeAPIError(w, http.StatusBadRequest, "USER_ID_INVALID")
return
}
member, err := h.gateway.BotAPIChatMember(r.Context(), botID, chatID, userID)
if err != nil {
writeAPIError(w, http.StatusBadRequest, apiErrorDescription(err))
return
}
writeAPIOK(w, apiChatMember(member))
}
func (h *handler) getUpdates(w http.ResponseWriter, r *http.Request, botID int64) {
if h.gateway == nil {
writeAPIError(w, http.StatusNotImplemented, "METHOD_NOT_FOUND")